A language-agnostic coding standard.
The objective is to develop code and applications efficiently and with discipline, ensuring the following qualities :
- Adequate : Meets user needs perfectly.
- Efficient : Minimizes processing and waiting times, reducing frustration and maximizing productivity.
- Attractive : Provides a visually appealing interface.
- Ergonomic : Offers an intuitive and productive user experience.
- Robust : Remains stable, even in poor connectivity conditions.
- Secure : Protects data from loss, corruption, theft, or hacking.
- Maintainable : Can be easily fixed or improved by any team member.
- Extensible : Allows new features to be added easily through reusable components.
- Consistent : Appears as though designed and implemented by a single developer.
This coding standard emphasizes self-documenting code and prioritizes readability over compactness by :
- Prohibiting the use of cryptic acronyms, abbreviations, prefixes, or suffixes.
- Requiring class names to be part of the attributes and variable names.
-
Be a source of order, not chaos.
-
Always leave the code in a better state than you found it, so even if you're in a hurry :
- Immediately fix any design or implementation flaws you encounter, because if you do not, no one else will, and technical debt will accumulate, eventually crippling the entire project.
- Continue writing clean, maintainable code to limit software entropy, which will help you ship faster by sustaining high productivity as the project grows in size and complexity.
- Resist the temptation to write quick-and-dirty code, or you'll lose more time debugging and fixing it later.
-
Never waste performance, or poor performance will eventually come back to haunt you :
- Avoid repeating expensive operations like database queries at close intervals.
- Use short-term caches to reuse recent results of expensive operations.
- Don’t use original images and videos directly.
- Use only highly compressed images with smaller dimensions.
- Use dictionaries to avoid iterating multiple times over large arrays.
- Use efficient algorithms and data structures.
-
Design before you code by quickly drafting :
- A short text or UI flow to optimize the user interface before implementation.
- A brief document or database design to optimize the data architecture before implementation.
- A short description or component design outlining the application's structure to optimize the component architecture before implementation.
- A brief usage example or test code to optimize the component interface before implementation.
-
Develop programs incrementally, starting with the data components and adding external features one at a time.
-
Do not over-engineer your code, choose simple, modular designs that can be easily extended.
-
Do not repeat yourself, create reusable code and components that can be used across multiple projects.
-
Develop the application and its components with simple, efficient, and maintainable code that :
- Is stable and robust.
- Is easy to understand on its own, without requiring comments or additional context.
- Can be easily extended and debugged by any team member.
- Minimizes dependencies on external libraries, in line with a minimalist approach.
- Minimizes memory, CPU, disk, and network usage, ensuring quick load times and high performance even on low-end hardware.
- Minimizes software entropy by maintaining an orderly structure and fully adhering to coding standards.
-
Instead of adding comments to explain the code's intent, refactor the code to make that intent clear by :
- Using clear, consistent, and unambiguous names for all classes, attributes, functions, parameters, and variables.
- Using local variables to store intermediate results.
- Breaking down long functions into smaller, sequenced functions.
- Keeping functions focused so they only perform the task their name suggests, and nothing more.
- Writing readable, English-like code that even a child could understand.
-
Ensure the application is resilient to external conditions (e.g., network failures, missing or corrupted files).
-
Validate method parameters using assertions in debug builds.
-
For performance optimization, prioritize :
- Public attributes over getters and setters.
- Public non-virtual methods.
- Virtual methods over delegates.
- State classes over coroutines.
-
Use private attributes and methods only when necessary.
-
Create automated unit tests for all your code.
-
Only push stable changes after thoroughly testing them.
-
Develop features on their own branches.
-
Merge the feature branch changes into the develop branch and test them rigorously to ensure that they do not cause downtime or data loss.
-
Merge the develop branch changes into the release branch and test them again.
-
Merge the release branch changes into the master branch and test them once more.
-
Always use the concise functions provided by the project's high-level libraries, rather than directly calling the underlying low-level functions, to maintain readability and consistency in the code.
-
Keep the project as simple as possible, continuously removing unnecessary complexity in line with the "Less is more" philosophy.
-
Regularly update the project to the latest stable versions of all tools and dependencies it relies on.
- Write simple and efficient self-documenting code, where the intent of each line of code is perfectly obvious without any comment or context.
- Write short functions and methods which use clear non-ambiguous names for all type, function, variable and constant identifiers.
- Split your styling code using section comments : IMPORTS, CONSTANTS, VARIABLES, FUNCTIONS, MIXINS, FONTS, ELEMENTS, CLASSES.
- Split your imperative code using section comments : IMPORTS, CONSTANTS, VARIABLES, TYPES, ATTRIBUTES, CONSTRUCTORS, INQUIRIES, OPERATIONS, FUNCTIONS, STATEMENTS.
- Use Unix line endings.
- Use four spaces per indentation level and no tabulations.
- Put braces on their own line and align them vertically.
- Put JavaScript multiline return values in parentheses.
- Indent the content of braces, brackets and parentheses by four spaces.
- Indent closing brackets and parentheses by four spaces.
- Add exactly a space after: { [ ( if while for.
- Add exactly a space before: } ] ).
- No space between: {} [] ().
- Add an empty line before: if while for return.
- Split multiline arrow functions after the => operator.
- No empty line after { [ ( and before } ] )
- No consecutive empty lines.
- Do not use single letter variable names like a, b, i, j, n (except for x, y, z, w vector components).
- Use explicit identifiers without any abbreviated word (except for Id, Uuid, Tuid suffixes) : productCount, productArray.
- Use the class name in the variable names : addedProductId, productArray.
- Use the key name in the map names : messageByDateMap.
- Use the singular form in all identifiers (messageArray, messageByDateMap), except for functions (getMessages) and booleans (hasMessages).
- Use standard variable prefixes : first, last, post, prior, next, old, new, initial, final, minimum, maximum.
- Use standard variable suffixes : Index, Count, Array, Map, Id, Uuid, Tuid, Code, Name, Text, Time, Path.
- Use standard path suffixes : FolderPath, FilePath, FileName, FileLabel, FileExtension.
- Use standard function prefixes : get, find, is, has, clear, set, add, remove, start, stop, begin, end, enter, exit, open, close, read, write, enable, disable.
- Write all type identifiers in PascalCase : Product, ProductImage.
- Write all function, variable and constant identifiers in camelCase : product, productImage, productIndex, productCount, productArray, productByIdMap.
- Write each function parameter on its own line.
- Do not add an ending comma to array literals.
- Declare standard functions and methods instead of arrow functions.
- Use arrow functions only as call arguments.
- Declare HTML attributes in this order : id, class, style, data-, tag-specific, on.
- Order classes from low to high level : class="icon green-search-icon product-filter-button".
- Do not use quotes for evaluated HTML attributes : placeholder={ ... }.
- Use double quotes for HTML attributes : id="..." class="..." style="..." data-name="..." src="..." on:click={ ... }.
- Use single quotes for string literals.
- Use pre-incrementations and pre-decrementations : ++productIndex, --messageCount.
- Use strict equality operators : ===, !==.
- Test variables against a constant instead of using a falsy test : !== undefined, !== null, !== 0, !== ''.
- Use let to declare scope variables.
- Only use const to declare global constants.
- Use the script/style/HTML order in Svelte components.
- Immediately fix all warnings and errors that you see during compilation or in the runtime console.
- In Dart, write null safe code, using null values only where required (optional arguments, etc).
<script>
// -- IMPORTS
import { fade } from 'svelte/transition';
// -- VARIABLES
export let value = undefined;
export let valueArray = [ value ];
export onChange = () => {};
let valueCount = valueArray.length;
// -- TYPES
class Being
{
// -- CONSTRUCTORS
constructor(
name,
age
)
{
this.name = name;
this.age = age;
}
}
// ~~
class Person
extends Being
{
// -- CONSTRUCTORS
constructor(
name,
age,
weight
)
{
super( name, age );
this.weight = weight;
}
// -- INQUIRIES
getAge(
)
{
return age;
}
// ~~
getHelloMessage(
)
{
return `Hello, my name is ${ this.name }, I'm ${ this.age } years old and I weight ${ this.weight } kilograms.`;
}
// -- OPERATIONS
setAge(
age
)
{
this.age = age;
}
// ~~
setFakeAge(
age
)
{
if ( age > 0
&& age < 50
&& ( age < 20
|| age > 40 ) )
{
this.age = age * 2;
}
else if ( age > 20
&& age < 40
&& ( age < 25
|| age > 35 ) )
{
this.age = age * 0.5;
}
else
{
this.age = age + 10;
}
}
}
// -- FUNCTIONS
function getFruitText(
fruitArray
)
{
let fruitText = '';
for ( let fruitName of fruitNameArray )
{
if ( fruitText !== '' )
{
fruitText += ', ';
}
fruitText += fruitName;
}
return fruitText;
}
// ~~
function getAgeInterval(
sortedPersonArray
)
{
if ( sortedPersonArray.length === 0 )
{
return null;
}
else
{
return (
{
firstAge: sortedPersonArray[ 0 ].age,
lastAge: sortedPersonArray[ sortedPersonArray.length - 1 ].age
}
);
}
}
// -- STATEMENTS
let fruitNameArray =
[
'apple',
'banana',
'cherry'
];
let book =
{
title: 'SvelteKit for Beginners',
author: 'John Doe',
year: 2023
};
let numberArray = [ 1, 2, 3, 4, 5 ];
let doubledNumberArray = numberArray.map( n => n * 2 );
let bookText = '';
let messageCount = 5;
for ( let messageIndex = 0;
messageIndex < messageCount;
++messageIndex )
{
bookText += messageIndex + ': ' + book.title + '<br/>';
if ( book.year >= 2022 )
{
bookText += 'The book is recent!<br/>';
}
}
let passIndex = 0;
while ( passIndex < 5 )
{
++passIndex;
}
do
{
++passIndex;
}
while ( passIndex < 10 );
let fruitText = getFruitText( fruitNameArray );
let personArray =
[
new Person( 'Mike', 49, 85 ),
new Person( 'Luke', 30, 77 ),
new Person( 'John', 30, 72 )
];
personArray.sort(
( firstPerson, secondPerson ) =>
{
try
{
if ( firstPerson.age !== secondPerson.age )
{
return firstPerson.age - secondPerson.age;
}
else
{
return firstPerson.weight - secondPerson.weight;
}
}
catch ( error )
{
console.error( error.message );
}
}
);
let ageInterval = getAgeInterval( personArray );
</script>
<style lang="stylus">
// -- ELEMENTS
h1
{
font-size: 2rem;
color: #34628B;
}
h1:hover
{
font-size: 2.5rem;
}
p
{
color: #B24871;
}
// -- CLASSES
.author
{
color: #A80BC9;
}
.book-text,
.fruit-list
{
color: #2C28B8;
}
.person
{
font-size: 1.5rem;
font-weight: 700;
color: #2929BD;
}
</style>
<svelte:head>
<title>SvelteKit Sample</title>
</svelte:head>
<main>
<h1>
{ book.title }
</h1>
<p class="author">
Author: { book.author }
</p>
<div>
<input placeholder={ `Named ${ book.title }` } bind:value={ book.title }/>
</div>
<div>
<input placeholder={ `Written by ${ book.author }` } bind:value={ book.author }/>
</div>
<p class="book-text">
{@html bookText }
</p>
{#each numberArray as number }
<p class="number" in:fade>
Number : { number }
</p>
{/each}
<p>
Doubled numbers: { doubledNumberArray.join( ', ' ) }
</p>
<p id="fruit-list" class="fruit-list">
Fruits: { fruitText }
</p>
{#each personArray as person }
<p class="person">
{ person.getHelloMessage() }
</p>
{/each}
<p>
{ ageInterval.firstAge } - { ageInterval.lastAge }
</p>
</main>
// -- IMPORTS
import { getMapByCode, logError } from 'senselogic-gist';
import { databaseService } from './database_service';
// -- FUNCTIONS
class CountryService
{
// -- CONSTRUCTORS
constructor(
)
{
this.cachedCountryArray = null;
this.cachedCountryArrayTimestamp = 0;
this.cachedCountryByCodeMap = null;
}
// -- INQUIRIES
async getCountryArray(
)
{
let { data, error } =
await databaseService.getClient()
.from( 'COUNTRY' )
.select();
if ( error !== null )
{
logError( error );
}
return data;
}
// ~~
async getCountryByCode(
countryCode
)
{
let { data, error } =
await databaseService.getClient()
.from( 'COUNTRY' )
.select()
.eq( 'code', countryCode );
if ( error !== null )
{
logError( error );
}
if ( data !== null )
{
return data[ 0 ];
}
else
{
return null;
}
}
// -- OPERATIONS
clearCache(
)
{
this.cachedCountryArray = null;
this.cachedCountryByCodeMap = null;
}
// ~~
async getCachedCountryArray(
)
{
if ( this.cachedCountryArray === null
|| Date.now() > this.cachedCountryArrayTimestamp + 300000 )
{
this.cachedCountryArray = await this.getCountryArray();
this.cachedCountryArrayTimestamp = Date.now();
this.cachedCountryByCodeMap = null;
}
return this.cachedCountryArray;
}
// ~~
async getCachedCountryByCodeMap(
)
{
if ( this.cachedCountryByCodeMap === null
|| Date.now() > this.cachedCountryArrayTimestamp + 300000 )
{
this.cachedCountryByCodeMap = getMapByCode( await this.getCachedCountryArray() );
}
return this.cachedCountryByCodeMap;
}
// ~~
async addCountry(
country
)
{
this.clearCache();
let { data, error } =
await databaseService.getClient()
.from( 'COUNTRY' )
.insert( country );
if ( error !== null )
{
logError( error );
}
return data;
}
// ~~
async setCountryByCode(
country,
countryCode
)
{
this.clearCache();
let { data, error } =
await databaseService.getClient()
.from( 'COUNTRY' )
.update( country )
.eq( 'code', countryCode );
if ( error !== null )
{
logError( error );
}
return data;
}
// ~~
async removeCountryByCode(
countryCode
)
{
this.clearCache();
let { data, error } =
await databaseService.getClient()
.from( 'COUNTRY' )
.delete()
.eq( 'code', countryCode );
if ( error !== null )
{
logError( error );
}
return data;
}
}
// -- VARIABLES
export let countryService
= new CountryService();
// -- TYPES
class Property
{
// -- ATTRIBUTES
final String
id;
final double
number;
final String
city,
country,
title,
description;
final double
price;
final String
imagePath;
final List<String>
imagePathArray;
final Map<String, String>
propertyByNameMap;
// -- CONSTRUCTORS
Property(
{
required this.id,
required this.number,
required this.city,
required this.country,
required this.title,
required this.description,
required this.price,
required this.imagePath,
required this.imagePathArray,
required this.propertyByNameMap
}
);
// -- OPERATORS
@override
bool operator==(
Object other
)
{
if ( identical( this, other ) )
{
return true;
}
else
{
return
other is Property
&& other.id == id;
}
}
// -- INQUIRIES
Property copyWith(
{
String? id,
double? number,
String? city,
String? country,
String? title,
String? description,
double? price,
String? imagePath,
List<String>? imagePathArray,
Map<String, String>? propertyByNameMap
}
)
{
return Property(
id: id ?? this.id,
number: number ?? this.number,
city: city ?? this.city,
country: country ?? this.country,
title: title ?? this.title,
description: description ?? this.description,
price: price ?? this.price,
imagePath: imagePath ?? this.imagePath,
imagePathArray: imagePathArray ?? this.imagePathArray,
propertyByNameMap: propertyByNameMap ?? this.propertyByNameMap
);
}
// ~~
factory Property.fromMap(
Map<String, dynamic> map
)
{
return Property(
id: map[ 'id' ],
number: map[ 'number' ],
city: map[ 'city' ],
country: map[ 'country' ],
title: map[ 'title' ],
description: map[ 'description' ],
price: map[ 'price' ],
imagePath: map[ 'imagePath' ],
imagePathArray: List<String>.from( map[ 'imagePathArray' ] ),
propertyByNameMap: Map<String, String>.from( map[ 'propertyByNameMap' ] )
);
}
// ~~
Map<String, dynamic> toMap(
)
{
return
{
'id': id,
'number': number,
'city': city,
'country': country,
'title': title,
'description': description,
'price': price,
'imagePath': imagePath,
'imagePathArray': imagePathArray,
'propertyByNameMap': propertyByNameMap
};
}
// ~~
@override
String toString(
)
{
return 'Property { id: $id, number: $number, city: $city, country: $country, title: $title, description: $description, price: $price, imagePath: $imagePath, imagePathArray: $imagePathArray, propertyByNameMap: $propertyByNameMap }';
}
// ~~
@override
int get hashCode
{
return id.hashCode;
}
}
// -- IMPORTS
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:senselogic_gist/senselogic_gist.dart';
import '../state/view_properties_state.dart';
import '../storage/storage.dart';
import '../store/view_properties_store.dart';
// -- TYPES
class ViewPropertiesPageState
extends State<ViewPropertiesPage>
{
// -- ATTRIBUTES
late final ViewPropertiesStore
propertyListStore;
// -- OPERATIONS
@override
void initState(
)
{
super.initState();
propertyListStore = ViewPropertiesStore();
propertyListStore.fetch();
}
// ~~
@override
Widget build(
BuildContext context
)
{
return Scaffold(
appBar: AppBar(
title: const Text( 'Property Details' ),
actions: [
IconButton(
icon: const Icon( Icons.home ),
onPressed: ()
{
context.go( '/' );
},
),
IconButton(
icon: const Icon( Icons.login ),
onPressed: ()
{
context.go( '/signin' );
},
),
IconButton(
icon: const Icon( Icons.person_add ),
onPressed: ()
{
context.go( '/signup' );
}
)
]
),
body: BlocConsumer<ViewPropertiesStore, ViewPropertiesState>(
bloc: propertyListStore,
listener:
( context, state )
{
},
builder:
( context, state )
{
if ( state is ViewPropertiesInitialState )
{
return const Text( 'Initial' );
}
else if ( state is ViewPropertiesLoadingState )
{
return const Center( child: CircularProgressIndicator() );
}
if ( state is ViewPropertiesErrorState )
{
return Center( child: Text( state.error ) );
}
else if ( state is ViewPropertiesLoadedState )
{
return
ListView.builder(
physics: const ScrollPhysics(),
shrinkWrap: true,
itemCount: state.propertyList.length,
itemBuilder:
( context, index )
{
final
property = state.propertyList[ index ];
return ListTile(
title: Text( getLocalizedText( property.title ) ),
leading: CachedNetworkImage(
imageUrl: getStorageImagePath( property.imagePath, 640 ),
placeholder: ( context, url ) => const CircularProgressIndicator(),
errorWidget: ( context, url, error ) => const Icon( Icons.error )
),
onTap: () => context.go( '/property/${ property.id }' ),
);
}
);
}
else
{
return const Icon( Icons.error );
}
}
)
);
}
}
// ~~
class ViewPropertiesPage
extends StatefulWidget
{
// -- CONSTRUCTORS
const ViewPropertiesPage(
{
super.key
}
);
// -- OPERATIONS
@override
ViewPropertiesPageState createState(
)
{
return ViewPropertiesPageState();
}
}
-
Use American English everywhere.
initializeColor(); moveForward();
-
Use the English language order in compound identifiers.
enemyListByCategoryDictionary = getEnemyListByCategoryDictionary();
-
Use the meter as the default distance unit.
-
Use the second as the default time unit.
-
Use four spaces instead of tabulations, to make the code independent of the editor settings.
-
Use Unix line endings.
-
Trim trailing whitespace on save.
-
Choose short meaningful identifiers for class, attribute, method, constant and variable names, to prevent ambiguity and cognitive load.
-
Use standard prefixes :
- first, last, post
- prior, next
- sub, super, base
- initial, final
- old, new
- backward, forward
- left, right
- back, front
- bottom, top
- minimum, maximum
- lower, higher, upper
- horizontal, vertical
- local, global
-
Use standard suffixes :
- Index, Count
- Array (in JavaScript), List (in Dart), Map
- Code, Name, Text, Time, Path
- Id, Uuid, Tuid
-
Use standard verbs :
- initialize, update, finalize
- create, release, destroy
- build, apply
- clear, fill
- reset, set, get, find
- is, has
- add, remove
- addFirst, removeFirst
- addLast, removeLast
- start, stop
- begin, end
- enter, exit
- open, close
- read, write
- load, save
- pause, resume
- lock, unlock
- attach, detach
- enable, disable
- select, deselect
- activate, deactivate
- increment, decrement
- increase, decrease
- compress, decompress
- connect, disconnect
- send, receive
- grant, revoke
-
Name your types (classes, structures, enumerations, etc) in PascalCase without articles, and declare the base class on the next line.
class TankShell extends Bullet { Vector3 positionVector; Quaternion rotationQuaternion; ... }
-
Name your type members (methods, attributes, constants, etc) in camelCase, without articles.
shootShell( muzzle.positionVector, muzzle.rotationQuaternion );
-
Name your local variables and method parameters in camelCase, without articles.
void shootShell( Vector3 shellPositionVector, Quaternion shellRotationQuaternion ) { TankShell shotTankShell; ... shotTankShell = new TankShell( shellPositionVector, shellRotationQuaternion ); ... }
-
Do not use abbreviations or single-letter variables.
Tank findTank( int tankIdentifier, int firstTankIndex, int postTankIndex ) { int tankIndex; for ( tankIndex = firstTankIndex; tankIndex < postTankIndex; // no i, j, n, etc ++tankIndex ) { if ( tankArray[ tankIndex ].identifier == tankIdentifier ) { return tankArray[ tankIndex ]; } } return null; }
-
Avoid acronyms, and capitalize them in member names.
DatabaseUrl databaseUrl;
-
If a variable name collides with a predefined identifier, simply add a trailing underscore.
Class class_; class_ = new Class();
-
Use a noun or noun phrase for classes, constants, attributes and variable names.
-
Include the meaningful part of the class name in attribute and variable names.
Dictionary<string, Player> activePlayerDictionary; List<Enemy> closeEnemyList; Tank[] enemyTankArray; Vector3 initialShellPositionVector, tankVelocityVector; void shootShell( ) { Shell lastShotShell, shotShell; ... }
-
Start method names with a verb in the imperative mood (Set, Get, Find, ...).
-
Start boolean inquiry names with a verb in the indicative mood (Is, Has, Can, ...).
-
Declare the method parameters in the same order as in the method name.
bool findPlayerIndexByName( ref int playerIndex, string playerName ) { ... }
-
Use a positive affirmation for boolean variable and attribute names.
if ( gameIsPaused ) { ... }
-
If an attribute name starts like its owner class name, omit the common prefix.
class Tank { TankShell[] shellArray; bool isDamaged; void shootShell( TankShell tankShell ) { ... } }
-
Align matching braces.
bool canShoot( ) { return shotShellCount < maximumShellCount; } // ~~ void shootShell( Vector3 initialVelocityVector ) { ... }
-
Use braces for single statement blocks.
if ( loadedAmmunitionCount > 0 ) { shootBullet(); } else if ( carriedAmmunitionCount > 0 ) { reloadWeapon(); } else { noAmmunitionSound.play(); }
-
Declare each attribute, variable and method parameter name on a separate line.
int tankCount, tankIndex;
-
Group local variables of the same type, and sort the declarations by ascending types (lowercase, then PascalCase, then SCREAMING_CASE) and variable names, so that the declaration of a variable can be located at a glance.
int shellCount, shellIndex, tankCount, tankIndex; string playerName, targetName; CharacterController characterController; NavMeshAgent navigationMeshAgent; Tank enemyTank; Tank[] enemyTankArray;
-
Split complex statements and expressions on several lines if they contain boolean operators or if they become too wide.
-
When splitting an expression on several lines, start the next lines with an operator and align it with the start of its left operand (or else indent it by 4 spaces).
if ( ( tower.getDistance( towerTarget, weaponType ) > tower.maximumShootingDistance ) || ( tankDistance > maximum distance && tankHealth > 0.5 ) ) { ... } let shellHitsTower = ( ( tower.getDistance( towerTarget, weaponType ) > tower.maximumShootingDistance ) || ( tankDistance > maximum distance && tankHealth > 0.5 ) );
-
To ease code indentation, if the multiline expression starts with a bracket or a function call, the assignment operator can alternatively be put at the end of the first line :
<div on:handleShot= { ( shot ) => { let shellHitsTower = ( ( tower.getDistance( towerTarget, weaponType ) > tower.maximumShootingDistance ) || ( tankDistance > maximum distance && tankHealth > 0.5 ) ); } } > </div>
-
Favor indexed iterations over foreach iterations, and foreach iterations over functional iterations.
for ( enemyIndex = 0; enemyIndex < enemyList.count; ++enemyIndex ) { ... } foreach ( Enemy enemy in enemyList ) { ... }
-
Put the scalar or constant multiplier after the multiplicand expression.
averageValue = ( firstValue + secondValue ) * 0.5f;
-
Add exactly one space :
- after
(
[
,
- before
)
]
- after
if
switch
while
for
foreach
return
... - around operators.
- after
-
Add exactly one empty line :
- around standard comments;
- after the local variable declarations;
- after the method preconditions;
- between
if
switch
while
for
foreach
do
try
return
and the prior statement; - between
}
and the next statement.
-
Use standard file extensions.
- C# : cs
- C : c, h
- C++ : cpp, hpp
- JavaScript : js
- HTML : html
- CSS : css
-
Declare one class per source code file.
-
Use the class name in lowercase as file name.
tank_shell.cpp tank_shell.hpp
-
Group the class elements by category, and declare them in this order :
- Imports.
- Types.
- Constants.
- Attributes.
- Constructors.
- Destructor.
- Operators.
- Inquiries : methods which do not change the class attributes.
- Operations : methods which may change the class attributes.
-
Within a category, declare :
- the called methods before the calling methods, preferably in the order they will be called, so that the class code can be immediately understood by a single sequential read.
- the static members after the non-static members.
-
Import exactly what each file needs to be compiled independently, and nothing more.
-
Sort the imports by ascending names.
-
Declare code sections in this order :
- imports
- constants
- types
- variables
- functions
- statements
-
Delimitate code sections with standard comments.
// -- IMPORTS ... // -- CONSTANTS ... // -- TYPES class Name { // -- CONSTANTS ... // -- ATTRIBUTES ... // -- CONSTRUCTORS ... // -- DESTRUCTOR ... // -- OPERATORS ... // -- INQUIRIES ... // -- OPERATIONS ... } // -- VARIABLES ... // -- FUNCTIONS ... // -- STATEMENTS ...
-
Do not use standard comments for empty sections.
-
Align multiple lines comments with the surrounding statements, start them with an uppercase character and end them with a period.
/* A long explanation which must be written on several lines. */ ...
-
Align single line comments with the surrounding statements, start them with an uppercase character and end them with a period.
// A short explanation which can be written on a single line. ...
-
Put end of line comments exactly four spaces after the statement, and start them with lowercase character.
doSomethingWeird(); // a short explanation
-
Name the unit test class by simply adding a
_TEST
suffix to the class name.
- Use the string slice() method instead of substring().
-
Use double quotes for string literals.
-
Name your ids and classes in kebab-case, without articles.
-
Start modifier classes with a verb in the indicative mood (is-, has-, ...).
// -- CLASSES .is-hidden { display: none !important; } .button { ... } .button.is-active { ... } .button.is-selected { ... }
-
Use :
- unitless values for line heights.
- rem values for font sizes and all other static sizes.
- px values only for tiny static sizes, like 1 or 2 pixels.
- %, vh, vw, vmin and vmax values for dynamic sizes.
- zero or one decimal in your sizes (0.9rem, 1.2vh), unless you absolutely need more precision (0.75rem, 1.25rem).
-
Group rules by category, and declare them in this order :
- Imports
- Constants
- Variables
- Functions
- Mixins
- Fonts
- Elements
- Classes
-
Delimitate those sections with standard comments.
// -- IMPORTS ... // -- CONSTANTS ... // -- VARIABLES ... // -- FUNCTIONS ... // -- MIXINS ... // -- FONTS ... // -- ELEMENTS ... // -- CLASSES ...
-
Order rules by increasing interiority, appearance order, and specificity.
-
For a same base selector class, declare rules in this order :
- @keyframe class-animation
- .class
- .class:not()
- .class:first-child
- .class:nth-child()
- .class:last-child
- .class:hover
- .class:focus
- .class:active
- .class:invalid
- .class:before
- .class:after
- .class:placeholder-shown
- .class::placeholder
- .class ~ .other-class
- .class + .other-class
- .class > .other-class
- .class .other-class
- .class#id
- .class.other-class
-
Declare concatenated specifiers in this order :
- element
- #id
- .class
- :not()
- :first-child
- :nth-child()
- :last-child
- :hover
- :focus
- :active
- :invalid
- :before
- :after
- :placeholder-shown
- ::placeholder
-
Declare media queries inside the rule using the "+Media" mixin, right after the declarations, and order them by increasing breakpoint.
.class { ... @media ( min-width: 40em ) { ... } @media ( min-width: 60em ) { ... } }
-
Group declarations by category :
- Inheritance
- Position
- Container
- Layout
- Content
- Typography
- Behavior
-
Delimitate declaration sections with a blank line.
.header-menu { z-index: 100; position: fixed; top: 0; left: 0; right: 0; padding: 1rem; display: flex; justify-content: center; align-items: center; background-color: transparent; transition: background-color 0.3s ease; +Media( min-width-40em ) { padding: 2rem; } +Media( min-width-60em ) { padding: 3rem; } }
-
Declare properties in this order :
- Inheritance
- @extend
- Position
- z-index
- position
- top
- bottom
- left
- right
- transform-origin
- transform
- scale
- rotate
- translate
- Container
- container-type
- overflow
- overflow-y
- overflow-x
- box-sizing
- scrollbar-width
- inset
- margin
- margin-block
- margin-block-start
- margin-block-end
- margin-inline
- margin-inline-start
- margin-inline-end
- margin-top
- margin-bottom
- margin-left
- margin-right
- outline
- height
- min-height
- max-height
- width
- min-width
- max-width
- aspect-ratio
- border
- border-top
- border-bottom
- border-left
- border-right
- border-width
- border-style
- border-color
- border-image
- border-radius
- border-top-radius
- border-top-left-radius
- border-top-right-radius
- border-bottom-radius
- border-bottom-left-radius
- border-bottom-right-radius
- border-collapse
- padding
- padding-top
- padding-bottom
- padding-left
- padding-right
- Layout
- display
- flex
- flex-direction
- flex-wrap
- flex-grow
- flex-shrink
- flex-basis
- flex-flow
- grid-template
- grid-template-rows
- grid-template-columns
- grid-template-areas
- grid-auto-rows
- grid-gap
- gap
- row-gap
- column-gap
- grid-area
- grid-row
- grid-column
- justify-content
- justify-items
- justify-self
- align-content
- align-items
- align-self
- order
- float
- clear
- columns
- column-count
- column-width
- column-span
- column-fill
- column-gap
- column-rule
- column-rule-width
- column-rule-style
- column-rule-color
- shape-outside
- clip-path
- object-fit
- Content
- content
- visibility
- opacity
- background
- background-clip
- background-color
- background-image
- background-origin
- background-position
- background-repeat
- background-size
- background-attachment
- mask
- mask-clip
- mask-composite
- mask-image
- mask-mode
- mask-origin
- mask-position
- mask-repeat
- mask-size
- box-shadow
- filter
- backdrop-filter
- Typography
- list-style-type
- direction
- writing-mode
- text-orientation
- line-height
- font
- font-family
- font-size
- font-size-adjust
- font-optical-sizing
- font-weight
- font-style
- font-stretch
- font-kerning
- font-language-override
- font-feature-settings
- font-variant
- font-variant
- font-variant-alternates
- font-variant-caps
- font-variant-east-asian
- font-variant-emoji
- font-variant-ligatures
- font-variant-numeric
- font-variant-position
- font-variation-settings
- hyphens
- white-space
- overflow-wrap
- word-wrap
- word-break
- word-spacing
- letter-spacing
- caption-side
- vertical-align
- text-align
- text-align-last
- text-indent
- text-justify
- text-overflow
- text-decoration
- text-decoration-line
- text-decoration-style
- text-decoration-color
- text-transform
- text-shadow
- color
- Behavior
- resize
- scroll-behavior
- scroll-snap-type
- scroll-snap-points-y
- scroll-snap-points-x
- user-select
- pointer-events
- cursor
- transition
- transition-property
- transition-delay
- transition-duration
- transition-timing-function
- animation
- animation-name
- animation-delay
- animation-duration
- animation-timing-function
- animation-iteration-count
- animation-direction
- animation-fill-mode
- animation-play-state
- Media queries (of increasing breakpoint size)
- Inheritance
-
Write color literals in UPPERCASE.
background-color: #C8C8C8;
-
Declare Stylus constants in camelCase.
redColor = #F87272; redColor100 = darken( redColor, 80% );
-
Declare SCSS constants in kebab-case.
$red-color: #F87272; $red-color-100: mix( $red-color, #000000, 80% );
-
Use double quotes for string literals.
-
Use single quotes inside double-quoted string literals.
-
Declare tag attributes in this order :
- id
- class sorted by increasing specificity, but ending with the modifier classes
- style sorted as explained above
- data-* in alphabetical order
- tag-specific attributes (src, href, etc) in alphabetical order
- on* in alphabetical order
<div id="header-menu-home-button" class="button scaled-button header-menu-button header-menu-home-button is-active is-selected is-hidden" style="margin-left: auto; background-image: url( '/static/image/header_menu/home_button.svg' )" data-view-name="home" onclick="ShowView( 'home' )"> ... </div>
- Use the following order in Svelte components :
- script section
- style section
- HTML section
- Routes are written in kebab-case.
- Local files routes start by /local/.
- Uploaded files routes start by /upload/.
- CDN files routes start by /global/.
- Administration routes start by /admin/.
- API routes start by /api/.
- Page data routes use the POST verb.
- Assets are organized and named with the same care as code variables, using clear, unambiguous file and folder names in snake_case.
- File names are normalized to include only the following characters: letters, numbers, hyphens, underscores, and dots.
- Documents are stored in a document/ subfolder, images in an image/ subfolder, and videos in a video/ subfolder, ideally named after the page to which they belong.
- User-specific files are stored in a user/{profile_id}/ subfolder.
- Uploaded images are saved with a millisecond timestamp suffix added to the end of their file name and kept in their original format or converted to a lossless AVIF file format.
- Local and uploaded original raster images are automatically converted to highly optimized AVIF files at all the resolutions in which they are actually used :
- image_name.360.avif (nano placeholder at 30% quality)
- image_name.480.avif (tiny 1/4 width at 55% quality)
- image_name.640.avif (small 1/3 width at 55% quality)
- image_name.960.avif (medium 1/2 width at 55% quality)
- image_name.1280.avif (wide 2/3 width at 55% quality)
- image_name.1920.avif (large 1x width at 55% quality)
- image_name.2560.avif (big 4/3 width at 55% quality)
- image_name.3840.avif (huge 2x width at 55% quality)
- Images are resized to match the target width while preserving the original aspect ratio, with the target height limited to twice the original width.
- Both the original and re-encoded images share the same relative path and file label.
- The website must use only the optimized images stored on the CDN, at the smallest appropriate resolution.
- A preloaded low-resolution placeholder image should be displayed while the high-resolution image loads.
- Database paths do not specify resolutions :
- /local/path/to/image_name.avif
- /global/path/to/image_name.avif
1.0
Eric Pelzer (ecstatic.coder@gmail.com).
This project is licensed under the GNU Lesser General Public License version 3.
See the LICENSE.md file for details.