KiwiJS Style Guide

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:


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 the devDependencies property "grunt-contrib-jshint": "~0.10.0" or similar.
  • Ensure your gruntfile.js has the initConfig property jshint: { }. 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:
  • 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--

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.

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.

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.

If you are breaking up a function’s parameters, indent subsequent lines by two tabs. This ensures the function block remains prominent.

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.

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.

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.

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.

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 <.

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 \".

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.

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.

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.

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.

Extension

When extending, use the built-in Kiwi method:

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.

Share the joy
Comments 1

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">