Skip to content

Coding Style

Jirka Dell'Oro-Friedl edited this page Sep 28, 2021 · 36 revisions

Naming

Florin-Character ƒ

The examples for the usage of FUDGE import its modules with names prefixed by the florin character. This is not mandatory but just a matter of taste. Some people prefer to use a simple f, you can choose whatever you like and also use names with more characters. However, if you enjoy using ƒ, here is how you can add a keyboard shortcut in VSCode for it: https://github.com/JirkaDellOro/FUDGE/issues/39

Self-explanatory

The developers code must be self-explanatory. This requires the strict use of naming conventions. Use names, that clearly explain the activity or information addressed and don't be greedy with letters. Short names are allowed only in very small scopes or when their meaning is clear by convention, such as y for a vertical position.

Variables and functions

The names of variables and functions must start lowercase and follow the camelCase notation, with uppercase letters indicating the start of a new part in a compound name such as animalLion. The names of variables describe an information or an object. Names of functions and methods strictly describe activities e.g. calculateHorizontalPosition(...) or questions e.g. isHit()

Formal parameters

Name formal parameters in a functions signature like variables, but prefix them with an underscore: (_on: boolean)

Classes, interfaces and namespaces/modules

Names of classes, interfaces and namespaces or modules start with an uppercase Letter and then follow the camelCase notation (PascalCase). The name describes exactly one object of that type, not an activity e.g. ObjectManager.

Enums

The names of enumerations and their elements are written all uppercase, with underscores separating parts of the name e.g. EVENT_TYPES.EXIT_FRAME

Calculated Enums

Functions or static methods that create, calculate and return a result independent of the state of the application or any objects are considered "calculated enumerations". Thus, the style for regular Enums apply with the addition of a signature, e.g. ROTATION_X(_angleInDegrees: number): Matrix4x4

Static stateless methods

Static methods of a class, that do not alter any state, but solely create and return a value or object for further processing, may be named in such a way as to describe the returned value and be written all uppercase. Like enumerations or constants, they represent values, but are calculating these at runtime. For example Vector3.DIFFERENCE(_a: Vector3, _b: Vector3): Vector3 returns a new Vector calculated by subtracting vector _b from vector _a.
For comparison, the non-static method Vector3.subtract(_subtrahend: Vector3): void alters the vector it is called upon by subtracting the '_subtrahend' from it while not returning a value.

Avoid ambiguities

Bad example from the DOM-API: getElementById(...) vs getElementsByTagName(...). Only the little s in the middle indicates that one returns a collection, not a single element. Better: getElementCollectionByTagName(...). However, in getElements(...), the s is clearly visible.

Use context and reduce redundancy

For example, state may have different meaning depending on the context. Machine.state indicates something different than Address.state. However, it is redundant to write Machine.stateTheMachineOperatesIn or Address.stateAsThePoliticalEntity, since the context is provided already by the namespaces. Use this instead of implementing redundancies.

Prefixes

Some prefixes may be helpful for finding names for variables, use is encouraged. Following is a list of prefixes that should be used.

Prefix Example Meaning
n nObjects an amount
i iObject an index
x, y, z xPos a direction or dimension
min, max maxHeight boundaries
pos, rot rotCube a position or rotation
cmp cmpMaterial a component
shd shdTexture a shader
mtr mtrSoil a material
clr clrRed a color
img imgHouse an image
mtt mttCamera a mutator
srl srlNode a serialization
mtx mtxLocal a matrix
vct vctPosition a vector
id idResource an identifier
hnd hndEvent a handler

Find new prefixes by determining the kind of information described (not necessarily the data type), stripping away the vowel and taking the first three letters.

Visibility & Scope

Always use visibility modifiers!

public

Though public is the default, also mark properties as public if they are

protected

For protected properties use the Florin (ƒ) as a prefix. This way, you can create accessors with the same name but without the Florin.

private

Wherever possible, use the javascript notation (#) for private instance fields, don't use the private keyword. This way, you can create accessors with the same name but without the #. This notation is not fully supported yet by Firefox and TypeScript doesn't manage the use on static fields (as of 2/21). Also, the mutator will not collect these properties! Where necessary, use the private keyword and the Florin (ƒ) as a prefix, just as for protected properties.

parameters

See https://github.com/JirkaDellOro/FUDGE/wiki/Coding-Style#formal-parameters

Comments

Use comments sparsely! If you feel that some code needs commenting rethink it and the naming of its components. Remember that you need to maintain comments just as you need to maintain code. Otherwise comments are not only useless but obstructive. Comments are allowed in the following cases

Separation

Regions in code may circle around a specific aspect and can therefore be separated by comments. However, if that aspect justifies the creation of a class concerning it, this should be preferred. To define a region use the following syntax in order to use the folding feature of editors
// #region NameOfTheRegion (and additional comment if necessary)
// #endregion

TODO

Mark unfinished business with a TODO comment. The IDE (-Plugins) can find, list and highlight those comments

Generation of documentation

This is required. TS-Doc comments must be inserted before each namespace class, interface, enum and public method and kept up to date. Use /**

Juridical

Comments about licensing or links to sources used etc.

Encapsulation and Accessors

A developer using a class should not be forced to learn its internal structure first, but must be enabled to intuitively work with it. This rule has some consequences on the design of such a class.

  • mostly, all attributes should be private or protected.
  • don't implement accessors just to alter or retrieve those attributes. If necessary, make them public.
  • however, if an attribute is not entirely intuitive like color, it should not be revealed, since the valid manipulation would require knowledge of internals. Instead, provide functions that describe a desired effect, so that the user can write for example Motor.start() instead of Motor.running = true
  • Using a accessors must not have sideffects other then what is needed to acquire or store the appropriate information. A user will not expect anything other than setting or retrieving a value. Use a method instead implies that there may be more complex things happening in the background and sideeffects altering the objects state. Compare object.x = 10 to object.setHorizontalPosition(10)

Structure

Size

A function should not consist of more than 20 lines of code. If possible, split it up into smaller functions each of which has an explanatory name. This way, the calling function consists of multiple calls that are easy to read and interpret, and the concerns are distributed to smaller functions with the same qualities. Also, watch out for the size of classes, beware of monsters! Keep the number of attributes low

Separation of Concerns

One function/method should care only about one concern and do this well

Indentation Depth

A function should not indent more than two levels. Use return statements not only at the end, but so called "early outs" and throw exceptions to keep indentation level low.

Top Down

Order functions and methods in such a way, that the call sits above the called function in code. Reading from top to bottom, the code displays the hierarchy of calls making it possible to understand the overall structure first before diving into the details.

Miscellaneous

Explicit types

Strictly use explicit typing wherever possible. The type any is prohibited.

Semicolons

Always end statements with semicolon.

Literal strings

Literal strings should be enclosed in double quotiation marks e.g. "Hello World!"

Magic numbers

Are simply disallowed. Never use a literal value in a function call when its meaning is not extremely obvious (e.g. Math.pow(x, 5) to retrieve x to the power of 5). In all other cases, define a variable with a explanatory name and assign the literal value to it. This way, there is a value and a meaning to it, and its value can be changed in a single place.

Files

Use one file per class. As an exception, tiny subclasses may be stored with the superclass. Interfaces may be stored with the class they are strongly connected to. Use PascalCase for filenames, exactly the same name as the classes.

Additional reading

https://github.com/basarat/typescript-book/blob/master/docs/styleguide/styleguide.md
https://github.com/Platypi/style_typescript
https://github.com/excelmicro/typescript

Clone this wiki locally