Skip to content

Coding Standard

Anthony Halim edited this page May 30, 2020 · 9 revisions

As always, we practice KISS and DRY within the project. In addition to that, we practice additional constraint to keep the code base consistent.


Class, Function, Methods


1. Class and Function Comments

Above each class definition, it is required to have comments on what is the class purpose within the application. The only class exempted from class comments are self-explanatory class (short class, ~<50 LoC). Additionally, state its requirements/dependencies (if any). Below is an example of class comment.

/**
 * This is an example of class comment.
 * The class fetches the assessment of students.
 */
class SampleClass extends React.Component<...> { ... };

Above each high-complexity function definition, it is required to have comments on what is the purpose of the function. The only functions exempted from function comments are self-explanatory functions (short function, ~<10 LoC). Additionally, state its requirements/dependencies (if any). Below is an example of function comment.

/**
 * This is an example of function comment. 
 * The function serves the purpose of: ...
 */
const sampleFunction = () => { ... };

Other than class and function comments, all other comments must use the single-line comments (\\). Good code should be self-explanatory and not need much comments to begin with. Hence, if you find yourself writing many comments, consider refactoring the code for clarity.

2. Class Template

2.1. Class Initialisation Template

We declare the attributes within a type State outside the class, in which will be used as part of the React.Component.

type State = {
   a: ... ;
   b: ... ;
};

class Example extends React.Component<..., State> {
  constructor() {
    this.state = {
      a = ...
      b = ...
    };
  }
}

2.2. Order of Class Methods

Within the class, the methods should be ordered as:

  1. Constructor
  2. Public methods
  3. Private methods

Within public or private methods, the methods should be ordered as:

  1. React component methods
  2. Handlers
  3. Helpers

Below is an example of order of methods.

class EgExample extends ... = () => {
  constructor()
  public componentDidMount()
  public render()
  private handlerEventA()
  private handlerEventZ() 
  private helperMethodA()
  private helperMethodZ()
}

Import and Exports


1. Relative paths vs absolute paths for import statements

Within this project, we restrict that all import paths should be of relative paths. This is to standardise with our testing that only accepts relative paths.

2. Import Order

Imports should be divided into three groups:

// node-module imports
import * from 'react';

// js-slang imports
import { Variant } from 'js-slang/variant';

// local imports
import SampleContainer from '../features/sample/SampleContainer';

Between import statements, the import statements must be ordered alphabetically based on its import path. Within a group import, the modules must be sorted alphabetically.

import {
  aaa,
  bbb,
  ccc,
  sss
} from ...; <-- Modules are ordered alphabetically within group imports

import zComponent from './aaa/component'
import aComponent from './zzz/component' <-- Alphabetical order based on import path

3. When to export default

export default should be conserved when there is one main component within the file. Anything other than the main component should be exported normally.

Similarly, when there is only ONE main component within the file, please use export default.


Interface and Types


Unless absolutely needed, prefer Types to Interface. Interface should only be used when there is overriding of particular attributes. If it is a union of attributes, please use types instead.

All interface name must be prepended with the letter I e.g. ISampleInterface.

type ComponentProps = DispatchProps & OwnProps & StateProps;
type DispatchProps = { ... };
type OwnProps = { ... };
type StateProps = { ... };
interface IParent {
    willBeOverriden: string | number | null;
    sharedAttr: number | null;
}

interface IChild extends IParent {
    willBeOverriden: string;        <-- type is overriden, meaningful interface
}

Do NOTs


1. Do NOT Export Nameless Functions or Modules

Always export functions or modules under a unique name. We provide the following example:

const ExampleContainer = withRouter<IPropType>(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(Example)
);

export default ExampleContainer;

Similarly when importing the module, do not rename the module as much as possible. We have seen the horror of programmer naming ability, and do not wish to perpetuate it. The following is an example of what should NOT been done:

import IWantToBeSpecialSoIRenameTheComponent from './ExampleContainer'

2. Do NOT Abuse Exports

If the interface or types are not used outside the module, please do not export it.


Usage of index.ts


Within this project, index.ts will only specifically refer to the root page i.e. src/index.ts.

Other than this, we do NOT welcome usage of index.ts in this project. While it has its uses under conventional practice, it has inflicted more confusion than benefits under this project.


Coding Style


1. Interfaces to prepended with 'I'

For example, ISampleInterface.

2. DispatchProps, StateProps, OwnProps

The three props must be defined at its component file. Do not rename it to anything else. The combination of the three props, must be named as ClassNameProps.

// In Workspace.tsx
type WorkspaceProps = DispatchProps & StateProps & OwnProps; // simple union
type DispatchProps = { ... };
type StateProps = { ... };
type OwnProps = { ... };

3. < > Format

Prefer one line format:

const EgExample extends React.Component<DispatchProps, {}> = () => { 
 ...
}

Compared to:

const EgExample extends React.Component<
  DispatchProps, {}
> = () => { 
 ...
}

4. Global Constants

All global constants must be stored at src/utils/constants.ts, and stored under Constants object. Thus, it will be accessed as constants.sampleGlobalVar. The constants themselves must be named in camelCase convention.

The only exception to this is action string constants.

5. Action String Constants

Action string constants must be of SHOUTY convention, e.g. FETCH_STUDENT_ID.

6. Component, Containers, and SubComponent Naming

Below is the proposed the following convention of file naming:

File Name Description
Component Parent Parent.tsx The component name is the file name.
Container of Component Parent ParentContainer.ts The component name is appended with Container.
Subcomponent of Parent: Child ParentChild.tsx The main component name must be prepended to the file name.

All file names must be of PascalCase. In the case where the first word is an abbreviation e.g. MCQ Question Component, we still enforce PascalCase convention i.e. McqQuestion.tsx.


Coding Standard Checklist


Before merging a PR, at least ensure the followings are fulfilled:

  • Ensure that all modules and functions are exported under a UNIQUE name (except for DispatchProps, StateProps, and OwnProps). Do a simple global search of the name within the project to ensure that it is unique.

  • Import container as container, component as component i.e. import AContainer from './AContainer' but not import A from './AContainer'

  • There is no single letter variable.

Clone this wiki locally