Coding Style Guide - bcgov/common-service-showcase GitHub Wiki
Coding Style Guide
In order to produce consistent, highly quality and readable code, our team follows certain coding styles and conventions. This document is a reflection of our team's commitment to continuous improvement. Please use this guide as a guidepost on how to keep the code reviews and the overall quality of code high.
Table of Contents
General Formatting
Independent of any programming languages, we strive to make sure that all of our source code content can be utilized in both Windows, Mac and Unix, with an emphasis on Unix conventions.
Repository Files
In each of our repositories, we leverage the following tools in order to programatically enforce certain formatting and styles:
- Editor Config - General coding styles for consistency across IDEs.
- For Visual Studio Code users, you will need this extension
- Git Attributes - Enforces certain source control formatting paradigms such as line endings.
- In our case, we want to force all text files to use Unix style line endings. This is necessary in order to prevent many headaches on Windows as we code for Unix platforms. Most of the time, this file will only contain the following line:
* text=auto eol=lf
- In our case, we want to force all text files to use Unix style line endings. This is necessary in order to prevent many headaches on Windows as we code for Unix platforms. Most of the time, this file will only contain the following line:
If you are starting out a new repository, one of the first few commits should be adding the above tools into source control as it will help ensure that all developers are able to produce consistently styled and formatted code. Check the following section for these example files to use.
Example Resources
- .editorconfig - Editor Config File
- .gitattributes - Git Attributes File
File Formatting
For all of our source code, please ensure that they adhere to the following guidelines:
- Unix style line endings - as that our deployment platform is Unix based, we want to ensure that all line endings use LF and not the Windows style CRLF ending. Make sure to check your git settings and have
autocrlf = true
orautocrlf = input
(ref). - Newline at end of source file - All file should have a newline at the end of source. This mainly prevents the
\ No newline at end of file
message from showing up in Git diffs. However, we also follow this style as that it makes whitespace changes at the end of a file much more succinct, and also prevents any chance of older legacy parsers from choking on file reads due to a lack of an ending newline. - UTF-8 File encoding - Unless there exists a reason to do otherwise, all source code shall be formatted in UTF-8 character encoding.
- Use special characters inline - Unless there exists a reason to do otherwise, it is best to use the special non-ASCII character directly as part of the code as it improves readability (i.e.
const units = 'μs';
vsconst units = '\u03bcs';
).
Logging
As with any application, there will usually be multiple levels of logging severity. With event logging, we attempt to conform to the syslog protocol as defined in RFC5424. Depending on the content that needs to be emitted, we suggest the following contexts in order of severity:
- error - Any code failures or exceptions
- warning - Any potentially concerning or unintended behaviors here
- info - Any general operation logging statements - Do NOT emit any potentially identifiable information and secrets at this level
- http - Only network level access event logging
- verbose - Any general operation logging statements which provides "in-depth" detail of the runtime/process. Consider this a noisier version of info level
- debug - Emit generalized debugging messages here - Potentially identifiable information and secrets MAY appear here ONLY with good reason depending on context
- silly / trace - If required, use this level for any function entry/exit tracing and anything else that would only be useful for fine-grained code behavior tracking
Whenever possible, when emitting logs, aim to ensure that the name of the function it is being emitted from is also a part of the log message. We suggest this convention as to ensure that we can more easily isolate any bugs or defects just from looking at the logs at a glance and know where to look next. For example, suppose you have a debug level statement coming from the generateDocument
method. You would ideally want a log statement which looks similar to the following:
debug generateDocument generated filename abc_123__.docx
Testing
Our team strives to ensure that all of our code is reasonably well unit tested in order to provide assurances that our code behaves as intended. We suggest the following guidelines:
- There should be a separate tests directory in the repository.
- This directory should be structured so that it has
common
,fixtures
,integration
andunit
test folders if they apply. - Common should store any test framework level constructs that are used across many tests. Fixtures should contain any static data objects that are used as part of testing. Integration tests should reside in here if they exist, and unit tests should reside in the unit test folder.
- This directory should be structured so that it has
- Whenever possible, attempt to mimic the code structure in the main source code, including its directory structure.
- Try to ensure there is a one-to-one correspondence with a source code file and its associated code test file.
- Group tests together based on logical functions and units of functionality.
- Whenever possible, describe the path to the component to be tested as discretely as possible. Suppose you are testing the
content
function inside the attachment object which is a part of the models class. A good descriptor for this would bemodels.attachment.content
as it shows where the function you are testing is in the object structure.
- Tests should be written to follow the Arrange, Act, Assert model (ref). While there will be exceptions, the AAA model helps improve test code readability.
- Ensure that your tests are as concise and specific as possible. While it is easy to get caught up in the environmental details which can affect the outcome of a test, strive to either mock or eliminate those variables from your tests so that they can test exact, specific and concise cases.
- It is easier to read many small discrete tests checking for assertions than it is to have a large test function which tests many conditions at once.
Languages
While the above guidelines apply to all types of source code, there are also additional conventions that we try to adhere to based on the language and framework being used.
Groovy
For the most part, Groovy code is mainly used in our Jenkinsfiles to dictate what needs to occur in our pipelines for deployment. While Groovy is very Java-like in syntax, we generally try to adhere to the Official Apache Groovy Style Guide as it emphasizes the use of more idiomatic and concise Groovy code. Key things of note are the following:
- 2 spaces for indentation
- No semicolons at the end - they are optional and are not needed at all
- Return statements are optional - the last evaluated expression is always implicitly the return value
- Prefer using
def
keyword over exact types - Groovy is an optionally typed language so let the language handle the type handling - Omit parentheses unless they improve code readability
- Use single quotes for string literals without interpretation
- Use double quotes for interpreted strings
Javascript
While there are plenty of Javascript style guidelines out there, we generally tend to loosely adhere to the Google Javascript Style Guide as that it generally aligns with most of the reasonable guidelines from ESLint recommended defaults. For our codebases in Javascript, we store our ESLint configurations in the eslintrc file or equivalent.
Documentation
We generally emphasize the use of JSDoc to annotate our code. This has the added benefit of allowing certain IDEs to immediately pick up on things such as code descriptions, parameters, return values, errors, and more upon hover-over, which can help other developers when they use our codebase. Each function should have an attached JSDoc to describe its function and purpose.
Formatting (JS)
In general, the Google style guide on formatting here provides a very good framework of the type of code style we would expect in the source code. Key things of note are the following:
- Use braces for all control structures
- 2 spaces for indentation
- Prefer using functional expressions (i.e.
(arg1, arg2) => { code stuff... }
) - Indent 2 spaces for switch statements
- Semicolons are required at end of statement
- Attempt to limit lines to 80 characters
Generally we will want to let our IDE's formatter handle the formatting as the .editorconfig file will express the desired formatting style.
Node.js
For the most part, Node.js does not have anything special on top of standard Javascript. The only major difference is that as of Node.js v12, we still need to import objects through the CommonJS syntax. At the time of writing (Jan 2020), native ES6 style imports are possible and allowed by default on Node.js v13 and above. However, until Node.js v14 LTS is out (which is what we use for production releases), we will continue to use the CommonJS style of import. A quick difference between the two formats is shown below:
CommonJS
const axios = require('axios');
Native ES6
import axios from 'axios';
Aside from the import differences, the only other main guideline is to focus on ensuring the code is logically broken down into logical components and parts in order to facilitate ease of code traversal.
React
For React code, we currently enforce the eslint:recommended
and plugin:react/recommended
guidelines and have them implemented as part of the package.json. While we do not have an official style guide at this time, one commonly used style is the Airbnb JavaScript Style Guide. Key things of note are the following:
- 2 spaces for indentation
- Prefer class notation for any components with internal state
- Avoid using mixins - they needlessly increase code complexity
- Use PascalCase for React components and camelCase for their instances
Generally we will want to let our IDE's formatter handle the formatting as the .editorconfig file will express the desired formatting style.
Vue
For any Vue code, we generally adhere to the Official Vue Style Guide. We will specifically attempt to adhere to their Priority A and B priorities. Key things of note are the following:
- 2 spaces for indentation
- Component names should always be multi-word to avoid collisions (ref)
- Data must always be a function (ref)
- Filenames of single-file components will always be in PascalCase
- Prefer self-closing components for components with no content (ref)
- Use PascalCase for referring to components (ref)
Generally we will want to let our IDE's formatter handle the formatting as the .editorconfig file will express the desired formatting style. For Visual Studio Code users, we leverage the Vetur plugin and let it manage our code formatting and consistency as its defaults generally align with the official Vue style guide.
Typescript
While there are plenty of Typescript style guidelines out there, we generally tend to loosely adhere to the Google Typescript Style Guide as that it generally aligns with most of the reasonable guidelines already in place from Javascript.
Formatting (TS)
In general, the Google style guide on formatting from the Javascript guide provides a very good framework of the type of code style we would expect in the source code. Key things of note are the following:
- Use braces for all control structures
- 2 spaces for indentation
- Prefer using functional expressions (i.e.
(arg1, arg2) => { code stuff... }
) - Indent 2 spaces for switch statements
- Semicolons are required at end of statement
- Attempt to limit lines to 80 characters
Generally we will want to let our IDE's formatter handle the formatting as the .editorconfig file will express the desired formatting style. Make sure if you are using VSCode that all workspace recommended extensions are installed, as auto-formatting should be in place to ensure your files are properly formatted on save.
SQL
While we do not currently have too much explicit SQL code in our codebases, we will ideally want to focus more on consistency than anything else. For any non-trivial SQL statements, we recommend using the Mozilla SQL Style Guide as a starting point.
YAML
We mainly use YAML in two places - the OpenAPI 3 specifications for our APIs, and any OpenShift / Kubernetes templates. Since YAML is technically a superset language of JSON (ref) and focuses on human readability, we suggest the following general guidelines:
- All YAML files shall start with an explicit
---
to denote the beginning of a YAML document- Should there be other YAML documents within the same file, they will also start with an explicit
---
- Should there be other YAML documents within the same file, they will also start with an explicit
- 2 spaces for indentation
- Always indent children elements
- Do not align values as this makes it harder to parse and manage
- Insert newlines between modules in an array
Generally we will want to let our IDE's formatter handle the formatting as the .editorconfig file will express the desired formatting style. An example of a well formatted YAML is as follows:
---
apiVersion: v1
kind: Template
labels:
app: "${APP_NAME}-${JOB_NAME}"
template: "${REPO_NAME}-template"
metadata:
name: "${REPO_NAME}-app-dc"
objects:
- apiVersion: secops.pathfinder.gov.bc.ca/v1alpha1
kind: NetworkSecurityPolicy
metadata:
name: "${APP_NAME}-app-${JOB_NAME}-pods-to-egress-${NAMESPACE}"
spec:
description: |
Allow pods to open connections to the internet
source:
- - "$namespace=${NAMESPACE}"
- "app=${APP_NAME}-${JOB_NAME}"
- "deploymentconfig=${APP_NAME}-app-${JOB_NAME}"
- role=app
destination:
- - ext:network=any
- apiVersion: secops.pathfinder.gov.bc.ca/v1alpha1
kind: NetworkSecurityPolicy
metadata:
name: "${APP_NAME}-app-${JOB_NAME}-pods-to-patroni-master-${NAMESPACE}"
spec:
description: |
Allow pods to open connections to patroni cluster
source:
- - "$namespace=${NAMESPACE}"
- "app=${APP_NAME}-${JOB_NAME}"
- "deploymentconfig=${APP_NAME}-app-${JOB_NAME}"
- role=app
- - "$namespace=${NAMESPACE}"
- "openshift.io/deployer-pod.type=hook-pre"
destination:
- - "$namespace=${NAMESPACE}"
- "cluster-name=${JOB_NAME}"
- "statefulset=patroni-${JOB_NAME}"
- role=master