Coding guidelines: Difference between revisions

From Seedbury Square
No edit summary
No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Parameters ==
== Parameters ==
Parameters will be divided into 3 broad categories, parentProps, required parameters and optional parameters.
Parameters will be divided into 3 broad categories: parentProps, required parameters, and optional parameters.
 
'''(parentProps is being phased out and being replaced with the static "methods" property of the Application class, eventually all classes will only have the params object as single parameter)'''


Required parameters '''will not''' have default values, they must be provided as arguments every time the class or function is used.
Required parameters '''will not''' have default values, they must be provided as arguments every time the class or function is used.
Line 15: Line 17:


This will make VSCode flag as an error any situation in which the class or function is being called without all required parameters.
This will make VSCode flag as an error any situation in which the class or function is being called without all required parameters.
==== Objects and Arrays as parameters ====
Something to keep in mind specifically when handling objects and arrays as parameters is to clone them as soon as they are received and when they are returned in a getter.
This creates an exact copy, but with a different reference to memory. This will allow the class to mutate the object/array if needed without breaking any logic outside of itself. It also allows editing the object/array outside the class after sending it as an argument without changing it inside the class in question. This will make debugging more straightforward, as all changes to the object/array used in the class were made in the same class.
To do this, use cloneObj(object/array).


=== Classes ===
=== Classes ===
Line 32: Line 41:
  setChecked(checked, { executeCallback, disable = false } = {}) { }
  setChecked(checked, { executeCallback, disable = false } = {}) { }
  setEntity(entity) { }
  setEntity(entity) { }
* The function is a baseElement that will receive a value in the overwhelming majority majority of cases. Example:
* The function is a baseElement or utility that will receive a value in the overwhelming majority majority of cases. Example:
  getParagraph(string, { size = 'medium', bold = false } = {}) { }
  getParagraph(string, { size = 'medium', bold = false } = {}) { }
* The function has a clear purpose that requires more than one required parameter and the label of those parameters is trivial. Example:
* The function has a clear purpose that requires more than one required parameter and the label of those parameters is trivial. Example:
Line 38: Line 47:


This does not mean that all required parameters in methods and functions go outside the destructured object. Even while being required, having them inside the params object forces the parameter to be clearly labeled, improving legibility. Example:
This does not mean that all required parameters in methods and functions go outside the destructured object. Even while being required, having them inside the params object forces the parameter to be clearly labeled, improving legibility. Example:
  addTeamMemberToProject(123, 456, 789)
  addTeamMemberToProject(123, 456, 789, { refresh: true })
vs
vs
  addTeamMemberToProject({ teamMemberId: 123, projectId: 456, selfEntityId: 789 })
  addTeamMemberToProject({ teamMemberId: 123, projectId: 456, selfEntityId: 789, refresh: true })


==== Amount of Parameters ====
==== Amount of Parameters ====
Line 48: Line 57:


== Private properties and methods ==
== Private properties and methods ==
Making properties and methods of classes private grants us a higher level of code security and discourages using functions and variables intended only for internal logic outside the class, possibly editing by reference, breaking internal logic, and introducing hard to track bugs. It makes the decision to allow certain methods to be used outside the class intentional.
Making properties and methods of classes private grants us a higher level of code security and discourages using functions and variables intended only for internal logic outside the class, possibly editing by reference, breaking internal logic, and introducing hard to pinpoint bugs. It makes the decision to allow certain methods to be used outside the class intentional.


In general, all properties should be private (except for the .view property, which should be public but read-only) and if a certain value is used outside of the class a getter and a setter function is created.
In general, all properties should be private (except for the .view property, which should be public but read-only) and if a certain value is used outside of the class a getter and a setter function is created.
=== Getters and Setters ===
=== Getters and Setters ===
When a private value needs to be read outside of the class, a getter method is created. It is always named get+variableName. Getter methods usually have no parameters. If the value to be read is an array or an object, it should be cloned before returning, creating a new identical object but with a different pointer. This way, if another class uses the getter method of this class, it can modify it for their own use without mutating the object used internally in this class, possibly causing bugs.
When a private value needs to be read outside of the class, a getter method is created. It is always named get+variableName. Getter methods usually have no parameters. The method will usually just return the private value, but in certain circumstances may do some verifications or changes first. If the value to be returned is an object or array, it should be cloned as outlined above.
 
When a private value needs to be mutated from the outside, a setter method is created. It is always named set+variableName. It usually receives a single argument, which is the value to be set. It may have more parameters to indicate if some callback should me invoked or status must be changed. This method verifies that the value received is valid and assigns it to the internal private property. If the value to be set is an object or array, it should be cloned as outlined above.


When a private value needs to be mutated from the outside, a setter method is created. It is always named set+variableName.
== Handling Service Responses ==
When consuming a service from the backend, it should be treated as a Promise. All these services will have a .then() and .catch().

Latest revision as of 14:42, 14 January 2026

Parameters

Parameters will be divided into 3 broad categories: parentProps, required parameters, and optional parameters.

(parentProps is being phased out and being replaced with the static "methods" property of the Application class, eventually all classes will only have the params object as single parameter)

Required parameters will not have default values, they must be provided as arguments every time the class or function is used.

Optional parameters may have default values, but they may also have no default value. No default value is preferred, and the possibility of an undefined value will be explicitly and intentionally handled. Some examples of correct default values for optional parameters include (but are not limited to):

  • The parameter is a constrained list of possible values (ex. size: 'small' | 'medium' | 'large').
  • The parameter is of type boolean, in which case false is preferred to undefined.

We will take advantage of type checking to help make sure the classes and functions are being used correctly. In the jsdocs, required parameters are defined as usual and optional parameters will be enclosed in brackets. Example:

* @param {AppProps} parentProps
* @param {object} params
* @param {number} params.requiredParameter
* @param {boolean} [params.optionalParameter]

This will make VSCode flag as an error any situation in which the class or function is being called without all required parameters.

Objects and Arrays as parameters

Something to keep in mind specifically when handling objects and arrays as parameters is to clone them as soon as they are received and when they are returned in a getter.

This creates an exact copy, but with a different reference to memory. This will allow the class to mutate the object/array if needed without breaking any logic outside of itself. It also allows editing the object/array outside the class after sending it as an argument without changing it inside the class in question. This will make debugging more straightforward, as all changes to the object/array used in the class were made in the same class.

To do this, use cloneObj(object/array).

Classes

When creating a class, the constructor will generally have 2 parameters: parentProps and an object called 'params' containing the rest of them. Some classes may not need parentProps, or a params object, or either of them; in those cases they are not added, which means some specific classes may instead have 1 or 0 parameters. Example of the typical class:

class Example {
  constructor(parentProps, {requiredParameter, optionalParameter = true, anotherOptionalParameter}) { }
}

When a class has a params object and all of the parameters are optional, the entire object must be given the default value of empty object. If at least one parameter is required, this default value should not be included. Example:

constructor(parentProps, {optional1, optional2 = 'small'} = {})
constructor(parentProps, {required, optional})

Methods and other functions

Parameter Structure

While classes will strictly adhere to the parentProps + paramsObject structure, methods and other functions (helpers or baseElements) will require a case by case analysis to determine the structure of parameters. A destructured object as the sole parameter should be the starting point; analyzing the behavior of the function and the most common way it will be invoked will inform any changes to be made. Some patterns to be considered when moving away from the sole object parameters are:

  • The method is a setter - it clearly states in the name what the value sent will be, and it should never be called without a specific value available. In this case it makes sense to have a singular required parameter followed by the destructured object of optional parameters (if any). Example:
setChecked(checked, { executeCallback, disable = false } = {}) { }
setEntity(entity) { }
  • The function is a baseElement or utility that will receive a value in the overwhelming majority majority of cases. Example:
getParagraph(string, { size = 'medium', bold = false } = {}) { }
  • The function has a clear purpose that requires more than one required parameter and the label of those parameters is trivial. Example:
addNumbers(number1, number2, { optional } = {}) { }

This does not mean that all required parameters in methods and functions go outside the destructured object. Even while being required, having them inside the params object forces the parameter to be clearly labeled, improving legibility. Example:

addTeamMemberToProject(123, 456, 789, { refresh: true })

vs

addTeamMemberToProject({ teamMemberId: 123, projectId: 456, selfEntityId: 789, refresh: true })

Amount of Parameters

Something to consider in methods and functions specifically is an appropriate amount of parameters.

As a general rule of thumb if a function has more than 3 required parameters, it is likely to be doing too many things. Functions should address a singular objective, and if they are growing to the point of needing more than 3 required parameters, chances are it is due for a refactor and breaking that functionality into more than 1 function/method.

Private properties and methods

Making properties and methods of classes private grants us a higher level of code security and discourages using functions and variables intended only for internal logic outside the class, possibly editing by reference, breaking internal logic, and introducing hard to pinpoint bugs. It makes the decision to allow certain methods to be used outside the class intentional.

In general, all properties should be private (except for the .view property, which should be public but read-only) and if a certain value is used outside of the class a getter and a setter function is created.

Getters and Setters

When a private value needs to be read outside of the class, a getter method is created. It is always named get+variableName. Getter methods usually have no parameters. The method will usually just return the private value, but in certain circumstances may do some verifications or changes first. If the value to be returned is an object or array, it should be cloned as outlined above.

When a private value needs to be mutated from the outside, a setter method is created. It is always named set+variableName. It usually receives a single argument, which is the value to be set. It may have more parameters to indicate if some callback should me invoked or status must be changed. This method verifies that the value received is valid and assigns it to the internal private property. If the value to be set is an object or array, it should be cloned as outlined above.

Handling Service Responses

When consuming a service from the backend, it should be treated as a Promise. All these services will have a .then() and .catch().