Version 1.0.0, 2014 October
Preamble
KiwiJS encourages community feedback and contributions. To make this easier for everybody, we require all code to meet certain standards prior to publishing. These standards address two main concerns: readability and functionality. Read this document twice; all code is interrelated and some stylistic elements refer to others that have come before.
Readability
This is for humans. Code should be immediately obvious and look as though it came from the same author.
Functionality
This is for machines. Code should be written to minimise common errors and make for stronger projects.
Sources and Further Reading
We have attempted to keep the KiwiJS style guide close to industry standards, where such exists. Our primary basis is the jQuery Javascript Style Guide, available under the MIT License. We have adapted the guidelines extensively, so please read through to make sure you understand our house standards.
We have also taken into consideration several other style guides. We do not necessarily agree with all of their propositions, but they are worth reading through for additional insight. Among our sources are:
- WordPress JavaScript Coding Standards
- Principles of Writing Consistent, Idiomatic JavaScript by Rick Waldron and contributors: a good discussion of why style matters, with an excellent further reading selection.
- Code Conventions for the JavaScript Programming Language by Douglas Crockford: practical justification for several style choices, from a perspective that’s not wedded to JavaScript.
- Coding in Style by Thomas M. Tuerke: a thoughtful discussion of style from a language-independent perspective.
- Google JavaScript Style Guide: a robust style guide with good justifications for its decisions.
Style
JavaScript versus TypeScript
KiwiJS is written in TypeScript, but compiles to and is distributed in JavaScript. For the most part the stylistic requirements remain the same, and most plugins are written in JavaScript. We will discuss JavaScript except where noted otherwise.
Linting
To “lint” is to check code for stylistic errors. There are several automated linting tools available. These do not take the place of proper coding, but at the very least, they will catch some common errors before they attract sharks.
We use JSHint to lint JavaScript. You can paste code into the form on the website and get immediate results. However, you will feel more pro if you use it from the command prompt, and it is also better for large projects involving several files. If you are using grunt you can get JSHint feedback via the following steps:
- Ensure your
package.json
has thedevDependencies
property"grunt-contrib-jshint": "~0.10.0"
or similar. - Ensure your
gruntfile.js
has theinitConfig
propertyjshint: { }
. This is where you add options. - Ensure the
jshint
property has a property pointing to the correct files:src: '//files to check//'
. - Ensure the
jshint
property has an options property mentioning (but not necessarily limited to) the following rules:123456789options: {camelcase: true,curly: true,eqeqeq: true,eqnull: true,newcap: true,quotmark: "double"} - Ensure your
gruntfile.js
uses this task, e.g.grunt.registerTask("default", ["jshint","uglify:build"]);
. It is usually best to call jshint first, so you don’t waste time compiling noncompliant code. - Now call grunt from the command line.
grunt
will run your default task.grunt jshint
will run the jshint task alone.
If jshint identifies a problem, but you are satisfied that you absolutely must use that particular kind of code, try to convince another programmer of its necessity. They may well be able to help you find a nicer solution. If they actually agree with you, you can tag your code to ignore certain errors; see the JSHint website for details.
Grunt can be a real timesaver once you start working with multiple files. Take the time to learn its ways.
Whitespace
Spacing
Use single spaces between all keywords and operators, with the following exceptions:
- No space before comma:
function( param1, param2 )
- No space before semicolon:
var x = 1;
- No space between
function
and opening parenthesis:function()
- No space before object definition:
{ objProperty: 2 }
- No space in empty constructs:
{}, [], function()
- No space before increment operators:
i++, i--
1 2 3 4 5 6 7 |
// Example of good spacing: var myFunction = function( loop, step ) { for ( var i = 0; i < loopCount; i += step) { console.log( condition ? outTrue : outFalse ); } }; |
Do not leave whitespace at the end of a line, or on an empty line.
Indentation
Indent using tabs, not spaces. Set your editor to disable conversion of tabs to spaces, and always view special characters. We suggest a tabstop equal to 4 spaces.
Indent the contents of all blocks, closures, and switches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Examples of correct indentation: runAnotherFunction( function() { // Do stuff } ); switch( param ) { case 1: // Do stuff break; default // Do other stuff } |
Line Breaks
Always break onto a new line after a semicolon used as a statement terminator. Do not add comments after it.
Do not break the open brace {
onto a new line.
Always break the close brace }
onto a new line.
Always break if
, else
, for
, while
, try
, and catch
blocks onto new lines within curly braces, even if they are single-line statements. You may need to add code in the future, and it becomes difficult to tell what’s inside and outside the conditional block.
Never break the else
or catch
keyword onto a new line.
1 2 3 4 5 6 7 8 9 |
// Example of correct if/else breaks: if ( condition1 ) { // Do stuff } else if ( condition2 ) { // Do other stuff } else { // Do the last stuff } |
Lines should not be longer than 80 characters. Human-readable text is optimal around 60 characters per line, so this should be more than enough. There are two exceptions:
- If the line contains a comment with a long URL or command. This makes it easier to cut and paste without having to strip out comment symbols.
- If the line contains a regex literal. This prevents having to use the regex constructor which requires otherwise unnecessary string escaping.
If your code goes over 80 characters, break it up using newlines. Place newlines after operators: this prevents the possibility of automatic semicolon insertion.
1 2 3 4 5 6 7 8 9 |
// Example of correct operator line breaks: var result1 = aVeryLongParameter + anotherVeryLongParameter + aThirdVeryLongParameter; // Example of correct legibility in line breaks, using a ternary: var result2 = assessALengthyConditionalStatement ? outputIfTrue : outputIfFalse; |
If you are breaking up a function’s parameters, indent subsequent lines by two tabs. This ensures the function block remains prominent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Examples of correct parameter line breaks: var myFunction1, myFunction2; myFunction1 = function( aVeryLongParameter, anotherVeryLongParameter, aThirdVeryLongParameter ) { // Do stuff }; myFunction2 = function( aVeryLongParameter, anotherVeryLongParameter, aThirdVeryLongParameter ) { // Do stuff }; |
If you are breaking up a series of declarations in an object or array, indent subsequent lines by one tab. If you are breaking up an object declaration, place each object on a new line. If you are breaking up an array, use common sense.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Examples of correct array and object definition: var matrix, shortObject, params; matrix = [ 1, 0, 0, 1, 0, 0 ]; shortObject = { param: 0 }; params = { param1: true, param2: false, param3: "snowflake" }; |
If in doubt, use newlines to increase legibility.
Always end a file with a newline. This ensures that it will correctly concatenate with other files. Maybe your file joining software can account for this, but we can’t guarantee that for every user and software package.
Comments
Use comment blocks only for file headers and for API documentation. For all other comments, use single-line comments, even if the comment must be broken onto multiple lines.
Precede all comments with a blank line. Comments precede the code they explain. Do not append them to the end of a line.
Begin comments with a space and a capital letter. Terminate only complete sentences.
Comments should explain non-obvious code, denote major steps, etc. Comment frequently, but never unnecessarily.
You may use /* inline comments */
to annotate special parameters or when needed to support specific development tools.
1 2 3 4 5 6 7 8 9 10 11 |
// Examples of good commenting /* * API documentation for myFunction */ // This function is used as an example. var myFunction = function( param1, /* Optional */ param2 ) { // Do stuff }; |
Documenting using YuiDocs
We document using YuiDocs. We highly recommend using YuiDocs to document your objects, properties, and methods. It adds clarity to the raw code such that you may not need further documentation, and generates useful API reference in HTML. The most common tags are @constructor
, @property
, @method
, @param
, @type
, @public
, @private
, @return
, @since
, and @deprecated
. Read through the documentation of the documentation to get a good feel for everything.
Declarations and Assignments
Declare all variables at the start of a function. This includes var
followed by this.property
declarations.
All var
declarations should made following a single var
at the beginning of a function. Unassigned declarations should be listed together at the top in alphabetical order. Break assignments onto new lines, also in alphabetical order. All new lines should be indented once. Because vars are hoisted to the beginning of the function when they run, this is just making declaration order explicit and easier to check by hand.
Note that this extends to the common use of var i
in loops. Iterators and loop-internal variables should be declared at the start of their parent function. Loops and braces do not enclose variables in Javascript; only objects (including functions) have their own scope.
You should consider defining a loop’s max value in a variable, rather than evaluating it every iteration. This is not a hard and fast rule, as loops can be useful in many different configurations.
You should always use succinct and descriptive labels for variables. Because you declare them in alphabetical order, you should give related variables related names so they are visually grouped together.
Because KiwiJS uses a lot of persistent object data, you should also declare object properties using this
directly after variables. Any object property that cannot be defined with a single-line assignment should be assigned null
and defined later. Because JavaScript is adept at adding properties to objects in realtime, you must be vigilant.
Append documentation to object properties in sequence.
You should define getters and setters alongside the other this
properties.
Declare empty objects as {}
and empty arrays as []
. These are most performant and most flexible if you later wish to fill them. Do not use new Object
or new Array
.
Declare object constructors as var ObjectName = function() {};
. Define internal data in the constructor.
Define object constants on the prototype, before methods. These constants should be used only within the object. If you are declaring constants that are used in multiple objects, see Namespaces.
Define object methods outside the constructor on the object prototype; this is more efficient than declaring methods every time new
is called, and makes it easier to extend objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// Examples of proper variable and property declaration var MyObject = function() { var i, myVar1, myVar2, loopLength = 8, loopStep = 2, myData = { a: "a" }; /** * A point. * @property point1 * @type Kiwi.Geom.Point * @public */ this.point1 = new Kiwi.Geom.Point( 0, 0 ); /** * Another point. * @property point1 * @type Kiwi.Geom.Point * @public */ this.point2 = null; // Function does other stuff // Example for loop for( i = 0; i < loopLength; i += loopStep ) { myVar2 = this.CONSTANT; myVar1 = i * myVar2; this.point2 = new Kiwi.Geom.Point( myVar1, myVar2 ); console.log( this.point1.distanceTo( this.point2 ) ); } }; MyObject.prototype.CONSTANT = 101; MyObject.prototype.method1 = function( param1, param2 ) { var myVar1, myVar2; // Do stuff }; |
Assignment in TypeScript
Follow the same guidelines when developing in Typescript. Because you declare properties outside the constructor in TypeScript classes, you should declare them directly after the constructor, along with any getters and setters.
Declaring Game Objects
This is a KiwiJS specific style element.
Add children to groups or states using addChild()
in a single code section of the Kiwi.State.create
method whenever possible. This makes it easier to see the initial state of the scene graph.
Try to keep addChild()
calls coherent during Kiwi.State.update
, but you only need to put them in a single section if you are adding to the scene graph without reordering. If you are using existing groups or doing reordering, you have more freedom.
1 2 3 4 5 6 7 8 9 10 |
var myBackground = new Kiwi.GameObjects.StaticImage( this, this.textures.background, 0, 0 ), myCharacter = new Kiwi.GameObjects.Sprite( this, this.textures.character, 100, 100 ); // Do stuff this.addChild( myBackground ); this.addChild( myCharacter ); |
Equality and Inequality
Always test equality using triple equality: ===
and !==
. This avoids type coercion in double equality; 1 == "1"
evaluates to true
, which is undesirable. You may use ==
and !=
to test for both null
and undefined
if necessary.
Do not test someProperty === true
. Simply checking someProperty
is sufficient. You may check non-boolean data in this way, where 0, null
, and undefined
are all false and other values are all true.
Always test the type of an object using typeof object === "string"
(or "number"
, "boolean"
, "object"
, "null"
, or "undefined"
).
Always test inequality with the variable on the left. This will quickly reveal null pointer exceptions. If you are checking two variables against one another, you should prioritise the most local variable. If locality is equal, just use the less than operator <
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var num1 = 1, num2 = 2, num3 = 1, str1 = "1"; // Return false console.log( num1 === num2 ); // Return true console.log( num1 === num3 ); // Return false console.log( num1 === str1 ); // Return false console.log( num1 < 0 ); // Return true console.log( num1 < num2 ); // Returns 2, 1 for ( ; num2; num2-- ) { console.log( num2 ); } |
Quotation Marks
Always use double quotes "
for strings. This allows you to embed apostrophes in your strings. It is also the string convention for languages like Java and C++. You can embed additional double quotes in strings with the escape character \"
.
1 2 3 4 5 6 |
// Ben's variable console.log( "Ben's variable" ); // Ben's "variable" console.log( "Ben's \"variable\"" ); |
Semicolons
Always add semicolons where required. Do not rely on automated semicolon insertion. Do not rely on tests for semicolons; it is possible, if rare, to accidentally create valid closures through insufficient semicolon use.
Semicolons must follow all assignments and statements, including the declaration of functions as variables.
Semicolons must not follow other code blocks, including conditionals, loops, and function declarations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Semicolons follow var i, myVar = 0; var myFunction = function() { // Do stuff }; myFunction(); // Semicolons do not follow if ( myVar ) { // Do stuff } switch( myVar ) { case 0: // Do stuff break; default: // Do stuff } for ( i = 0; i < 4; i++ ) { // Do stuff } function foo() { // Do stuff } |
Naming Conventions
All names should be succinct and descriptive. Group associated names with a common prefix. Do not use Systems Hungarian notation (e.g. numPositionX
for a numeric position x) as JavaScript has sufficiently strong types, but where useful, you may use expansive Apps Hungarian notation (e.g. unsafeInput
for an unsafe input that needs sanitising). In general, names should be meaningful in context and without further reference.
Variables and functions (and their equivalents, properties and methods) are named in camelCase: words are written in lower case with no separators, and subsequent words have their first letter in upper case.
Constructors are named in PascalCase: first letters of words are upper case, other letters are lower case, and there are no separators.
Namespaces should be in PascalCase.
Constants (values that are defined on namespaces or object prototypes and do not change) are named in SCREAMING_SNAKE_CASE with upper case words separated by underscores.
Variables, functions, constructors, namespaces, and constants should use only alphanumeric symbols (Aa-Zz,0-9), plus underscores in constants only. Do not use the dollar sign $
as this is most commonly associated with jQuery, and do not begin names with numbers. Do not use special characters such as ümlauts or カタカナ, as you cannot guarantee that other computers will even be able to read them, and other coders will find it impossible to work with your code. While it is technically possible to use spaces in the names of object properties (e.g. this[ "property with spaces in the name" ]
), don't do it. Treat proper nouns like "Hungary" or "Clark" and abbreviations like "HTML", "SQL" etc as lower-case words (hungary, clark, html, sql etc) and apply capitalisation as per the context (hungarySql
, CLARK_HTML
, etc).
File names should be named in spinal-case, and have lower case extensions. Names should be lower case and connect words or terms with hyphens. Version numbers should be specified in semver for plugins only. For example, fifth-dimension-plugin-1.0.0.js
and imp-in-a-bowler-hat.png
are both perfect. Hyphen-separated words are recognised by operating systems, allowing you to quickly double-click to select and replace words if you have to rename a file, and can be added to URLs without reformatting.
Nominally private data is denoted by a _leadingUnderscore: use the applicable naming convention, but prepend an underscore. This in no way makes the data private. It is still accessible for purposes of debug, but you should never access someObject._privateProperty
in code, only this._privateProperty
. If you need to create genuinely private data, such as for user account information or as anti-cheat measures, use closures.
Namespaces
Pack all code into a namespace. This is simply an object that keeps your code out of the global namespace. You may use additional namespaces to further sort your code.
When working on plugins, you should create a namespace under Kiwi.Plugins
, e.g. Kiwi.Plugins.MyPlugin
. There is an exception for plugins that must add to existing structures, in particular the Kiwi.Renderers
namespace, as Kiwi.Renderers.GLRenderManager.requestSharedRenderer()
and related functions can only see this namespace.
When working on games, you should give your name a namespace in the global scope, e.g. MyGame
. This namespace should contain your Kiwi.Game
object as game
and all your states, e.g. MyGame.game, MyGame.menu, MyGame.play
.
When working on multiple files that use the same namespace, you should ensure that the namespace exists before calling it in the following way: var MyNamespace = MyNamespace || {};
You may declare constants directly on namespaces if they are likely to be used by multiple objects or types of object. For example, Kiwi.Plugins.MyPlugin.MY_GLOBAL_CONSTANT = 101
is valid and useful. You may also declare constants as prototype properties, such as Kiwi.Plugins.MyPlugin.MyObject.prototype.MY_GLOBAL_CONSTANT = 101
, but these are intended for internal use by single types of object.
Functions
Use three kinds of function: constructors, methods, and anonymous functions or lambdas.
Constructors are declared using var
. They define objects via the new
keyword. Constructors should not contain methods or constants, only variables, properties, and initialisation calls. End constructors with semicolons.
Methods are declared on the prototype of objects. End methods with semicolons.
Anonymous functions are usually declared in callbacks, as parameters to other functions. Don't end these with semicolons unless they're somehow ending a statement.
A function should be tight. It should do one thing, not two. It should have a name beginning with a verb. It should not be more than 50 lines in length (in other words, it should be viewable on a single screen or page). It should not replicate code; any time you find yourself cutting and pasting code, consider making a new function instead.
A function should return a value whenever possible. This is not to say that all functions must return some signal. Some functions, like a render call, fulfill their purpose by performing a task; they have no business returning a value. In general, however, a function is more useful and more easily debugged when set to return. See Writing Testable JavaScript for some examples.
The return
statement should not use parentheses unless creating some manner of closure. It must begin on the same line, although you may use line breaks if it is very long.
A function should have no magic numbers. Declare any numbers as variables, properties or constants. An exception may be made for very obvious, invariant numbers such as 0 or 1 when no other number could possibly be used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Declare object var ValueStorer = function( startValue ) { this.startValue = startValue; this.operatedValue = null; }; ValueStorer.prototype.performOperation = function( operation ) { this.operatedValue = operation( this.startValue ); }; // Use object var valueStorer1 = new ValueStorer( 1 ), valueStorer2 = new ValueStorer( 2 ); // Store 0 in valueStorer1.operatedValue valueStorer1.performOperation( function( value ){ var factor = 0.4; return Math.floor( value * factor ); } ); // Store 2 in valueStorer2.operatedValue valueStorer2.performOperation( function( value ){ var factor = 0.9; return Math.ceil( value * factor ); } ); |
A function with more than one or two parameters, or with numerous parameters that have common defaults, should be designed to use a param object as its only parameter: param = {}
. The function should define default values for its parameters and override these with any values specified in the input param object. This makes function calls much more self-apparent.
1 2 3 4 5 6 7 8 9 10 |
var params, background; params = { state: this, texture: this.textures.background, x: 0, y: 0 }; background = createStaticImage( params ); |
A function should not be tricky. This whole guide is devoted to creating obvious, purposeful code. If your code looks like it should belong in this quiz, please don't. Even if you can make the code more performant through non-obvious tricks, other developers will lose so much time trying to understand it that there will be a net loss. If you must do something tricky (and this is a game engine, so sometimes realtime performance is vital), use extensive comments to explain what's actually happening, preferably with an unwrapped code version that is human-readable.
Ternaries
Consider using the ternary operator in place of short if/else
statements.
1 2 |
output = condition ? ifTrue : ifFalse; |
Extension
When extending, use the built-in Kiwi method:
1 2 3 4 5 6 7 8 9 |
var MyExtendingObject = function() { // Invoke constructor Kiwi.GameObjects.Sprite.call( this ); // Do additional constructor stuff }; Kiwi.extend( MyExtendingObject, Kiwi.GameObjects.Sprite ); |
The Forbidden Zone
Do not use eval
. It's a security risk and jshint will catch it.
Code Review
Code is not ready until somebody else has reviewed it.
Try to get another coder to look over your code before every commit.
Significant commits, particularly those for releases, must be reviewed by at least one additional coder. Append the names of your reviewers to any significant commit.
If you are using a non-git version control system, follow a similar policy.
Never develop without a version control system.
Conclusion
Read this document twice.
Once again, these rules are all in service to the simple principle that code should be human-readable and machine-functional. The art of code is constantly evolving. This should be considered a living document.
We welcome contributions to our own repositories that serve to bring old code up to new standards. We're all in this together.
Recent Comments