This repository contains a comprehensive set of conventions, standards and
guidelines that I have been using across numerous projects mainly in TypeScript
and Vue
.
These are not strict rules but rather guidelines to help me write better code. In certain cases, some guidelines may be broken. They may also change over time based on trends, tools, platforms, or technologies.
- Conventions, Standards and Guidelines
- Introduction
- Pillars of Quality Code
- Core Programming Concepts
- General Principle
- RESTful API
- Formatting and Styling
- ECMA Script 6 ES6 or Javascript and Nodejs
- Vuejs Includes HTML and CSS
- Tooling, IDE Extensions and Configurations
- Software Versioning and Version Control
- Conclusion
- Further Readings and Referrences
These conventions aim to promote code readability, consistency, and maintainability. They serve as a guide, not a set of strict rules, for writing clean and efficient code, enabling seamless collaboration among developers.
The purpose of having coding conventions, standards, and guidelines is to:
- Provide a uniform appearance to code written by different programmers and across different platforms.
- Improve code readability and maintainability while reducing complexity.
- Facilitate code reuse and make it easier to detect errors.
- Promote sound programming practices and increase programmer efficiency.
Everything should be done automatically without any mental cognitive load. Nowadays, we can take advantage of tools such as linters, formatters, or AI to handle these things. Read more in the tooling section to learn how to set it up.
Five pillars of quality code are:
- Readability
- Reusability
- Refactorability (Maintainability)
- Reliability
- Efficiency
Readability refers to how easily other developers (or even your future self) can understand the code. Code that is easy to read reduces the time required to grasp its functionality and logic.
Best Practices:
-
Clear Naming Conventions: Use meaningful variable, function, and class names that convey intent.
- Use camelCase for variable and function names.
- Use PascalCase for type and class names.
- Use SNAKE_CASE for constant names.
-
Consistent Formatting: Follow a consistent style guide for indentation, spacing, and braces.
-
Commenting and Documentation: Provide comments only where necessary that explains "why". Focus more on writing self-explanatory code.
-
Avoid Deep Nesting: Limit the levels of indentation to maximum of two to make code more accessible and easier to follow. Use guard clause to reduce number of nesting.
Reusability is the ease with which code components can be used across different parts of the application or even in different projects. Reusability allows us to express new ideas with little pieces of the past.
Writing reusable code helps reduce redundancy and promotes consistency.
Best Practices:
-
Modular Design: Break down functionality into small, self-contained modules or functions that can be easily reused.
-
DRY Principle (Don’t Repeat Yourself): Extract common functionality that are repeated three times or more into reusable utility functions or classes.
-
Parameterization: Write functions and methods that are flexible through the use of parameters, allowing them to handle various use cases.
-
Use of Libraries: Where applicable, use well-established libraries or frameworks that provide reusable functions or components.
Code should NOT be reusable when,
-
If you can't define a good API yet, don't create a separate module . Duplication is better than a bad foundation.
-
The function or module are not expected to be reused in the near future.
-
The duplication is NOT more than two. (See Rule of Three below)
Refactorability is the ease with which code can be improved, modified, or extended without introducing defects. This pillar focuses on the long-term maintainability of the codebase.
Things that we can do to make our code refactorable:
- Isolated side effects
- Tests
- Static types
- Rule of Three
A side effect occurs when a function or module modifies data outside its own scope, for example, writing data to a disk, changing a global variable, or printing something to the terminal.
A program consists of instructions that a computer executes to take data in and produce data out. If a program has no side effects, it's essentially a black box.
While side effects are necessary to make a program useful (since it has to modify something in the outside world), they should be carefully isolated.
Side effects make code hard to test because if a function's execution modifies some data that another function depends on, we can't be certain that the function will always produce the same output given the same input.
Side effects also introduce coupling between otherwise reusable modules. If module A modifies global state that module B depends on, then A must be executed before B.
Side effects make systems unpredictable. If any function or module can manipulate the application's state, it becomes difficult to predict how changing one part will affect the entire system.
To manage side effects, isolate them by creating a centralized location to update the global state of your application.
The Rule of Three: Don't refactor code until it's been duplicated at least twice. The first instance is acceptable, the second time you might copy, but by the third occurrence, it's time to refactor. This approach helps avoid premature optimization and unnecessary abstraction.
Best Practices:
- Loose Coupling: Design components to be independent of each other so that changes in one area don't ripple through the entire codebase.
- High Cohesion: Group related functionality together, ensuring that each module or class has a clear, singular purpose.
- Code Smells: Regularly check for and refactor code to eliminate code smells such as long methods, large classes, and unnecessary complexity.
- Version Control: Use systems like Git to track changes and facilitate collaboration and refactoring.
- Rule of Three: Apply refactoring when the same logic or code is duplicated three times, ensuring patterns are worth abstracting without premature optimization.
Reliability is the degree to which code functions correctly and consistently under expected conditions. Reliable code is robust and handles edge cases, errors, and unexpected inputs gracefully.
Best Practices:
-
Error Handling: Implement comprehensive error handling to manage exceptions and unexpected situations without crashing.
-
Unit Testing: Write unit tests to ensure that individual parts of the code work as expected.
-
Integration Testing: Use integration tests to verify that different modules or services interact correctly.
-
Input Validation: Always validate inputs to ensure they meet the expected criteria before processing.
Efficiency refers to the optimization of code in terms of performance and resource utilization. Efficient code executes quickly and uses minimal resources, such as CPU, memory, and network bandwidth.
Best Practices:
-
Optimized Algorithms: Choose or develop algorithms that are efficient in terms of time and space complexity.
-
Lazy Loading: Load resources only when they are needed to reduce initial load time and memory usage.
-
Tree Shaking: Remove unused code from the bundle. Code that is not imported or used is not included in the final bundle, resulting in a smaller codebase.
-
Caching: Implement caching strategies to store frequently accessed data, reducing the need for repeated computations or database queries.
-
Profiling and Optimization: Regularly profile the application to identify and optimize performance bottlenecks.
TBD
Think of a component as a Lego block.
You can choose various shapes, sizes, and colors when building a structure out of Legos. Some blocks are purpose-built to be doors, windows, and other structural elements. Each block has all the features it needs to connect to others, and adding or subtracting blocks generally has minimal impact on the structure.
5 features of components Software components have five common characteristics;
-
Reusability: Components plug into a variety of applications without the need for modification or special accommodations.
-
Extensibility: Components combine with other components to create new behaviors.
-
Replaceability: Components with similar functionality can be swapped.
-
Encapsulation: Components are self-contained and expose functionality through interfaces while hiding the details of internal processes.
-
Independence: Components have minimal dependencies on other components and can operate in different environments and contexts.
- Use
kebab-case
for file names - Use
LF
instead offor line endingCRLF
- Directory name should already convey the files within.
Common directory names are:
- apps: if a project have more than one applications (e.g: client, server, web, etc),
we group them under
apps
and uses workspace to manage them - bin: contains executable binary files or programs
- config: contains the application configuration files
- data: contains hard-coded data files in various format such as csv, json, etc that are loaded at runtime
- dist: contains compiled source and ready to be released, ignored by git
- docs: contains the source documentations
- logs: contain log files, ignored by git
- public: contains static files and commonly served by http server
- res or resources: contains all the non-code resources
- scripts: contains scripts files
- src: contains the application source files that are going to be compiled
- temp: contains temporary files, ignored by git
- test: contains the application tests such as unit tests, integration tests, etc
- uploads: contains uploaded files, ignored by git
- vendor: contains third-party libraries
Some common directory names specifically for web applications are:
- src/assets: similar to
res
- src/composables or src/hooks: contains the application hooks (in Vue, it's commonly known as composables)
- src/components: contains the application components
- src/lib: contains the application libraries
- src/pages: contains the application pages or views
- src/router or src/routes: contains the application router or routes
- src/services: contains the application services
- src/store: contains the application store
- src/utils: contains the application utils
-
Readable and Searchable Names
- Use clear, descriptive names that make the code easy to understand and maintain.
- Avoid abbreviations unless they are widely recognized (e.g.,
id
for identifier). - Names should be meaningful and reflect the purpose or role of the variable.
- Consistent across the entire codebase.
-
Capitalization Conventions
- Constants: Use
UPPER_CASE_SNAKE_CASE
for constants (e.g.,MAX_RETRY_ATTEMPTS
). - Variables: Use
camelCase
for regular variables (e.g.,userName
,totalAmount
). - Class Names: Use
PascalCase
for class names (e.g.,UserAccount
,OrderProcessor
). - Functions/Methods: Name functions or methods as verbs using
camelCase
(e.g.,calculateTotal
,fetchData
).
- Constants: Use
-
Pluralization
- If a variable holds a collection (e.g., an array or a map), use the plural form of the noun (e.g.,
users
,productList
). - For a Boolean, prefix with
is
,has
, orshould
(e.g.,isValid
,hasPermission
,shouldUpdate
).
- If a variable holds a collection (e.g., an array or a map), use the plural form of the noun (e.g.,
-
Contextual Clarity
- Avoid overly generic names like
data
,item
, orobj
. Add context to the name to clarify its use (e.g.,userData
instead ofdata
). - Use descriptive names for temporary variables in loop and indices. Avoid
i
,j
,k
.
- Avoid overly generic names like
-
Avoid Magic Numbers
- Replace magic numbers with named constants to improve readability (e.g.,
const MAX_ITEMS_PER_PAGE = 10
).
- Replace magic numbers with named constants to improve readability (e.g.,
-
Avoid Hungarian Notation
- Do not prefix variable names with data types (e.g., avoid
strName
,intCount
). Rely on TypeScript or other type-checking tools if needed.
- Do not prefix variable names with data types (e.g., avoid
-
Avoid Reserved Words
- Do not use JavaScript reserved words for variable names (e.g.,
class
,enum
,default
).
- Do not use JavaScript reserved words for variable names (e.g.,
-
A function or method should do ONE thing, and ONE thing only.
-
Guard Clause, don't write
else
unless it is a middleware. -
Consider extracting nested logic into separate functions. More than two levels of nesting can imply poor performance (in a loop), and it can be especially hard to read in long conditionals.
-
Limit Number of Function Arguments
- Limit number of arguments between 1-3.
- 0 arguments implies side effect.
-
Use Underscore for Private or Unused Variables
-
When dealing with private variables (especially in classes or modules), prefix them with an underscore (e.g.,
_privateData
). -
When defining a function that receives arguments, but some of them are not used, prefix the unused ones with an underscore.
function handleClick(event, _elementId) { // The `event` argument is used, but `_elementId` is not. console.log('Button clicked') // No use of `_elementId` }
-
When arrow Functions have unused arguments,
const numbers = [1, 2, 3] // We only care about the index, not the value. numbers.forEach((_value, index) => { console.log(index); // Outputs: 0, 1, 2 })
-
When destructuring an array,
const colors = ['red', 'green', 'blue'] // Destructure the array, but only the second element is used. const [_firstColor, secondColor, _thirdColor] = colors
-
Please refer to Rapid API Learning for comprehensive information about RESTful API.
Formatting and styling should be done automatically using linter. See Tooling section for more information how to set it up.
- Use spaces instead of tabs for indentation. Use two spaces to indent.
- Separate programming logic or contexts using a new line.
- Add a new line before
return
statement. - Add an empty line before the end of file. Use
LF
for file ending.
- Use relative path when related to browser so it can be imported using either npm, cdn link or importmap.
- Include file extension on file path when import a file, unless to an
index.js
import { formatDate } from 'src/utils/format-date.js'
import { formatDate } from 'src/utils // utils directory has index.js
Tailwindcss principles and style guides:
Currently I'm using eslint (without Prettier) to lint and format my code following the antfu/eslint-config configuration. Folowing the link below for more information.
By using pre-commit hooks, we can automatically lint and guard our code from code smells before committing. Currently using simple-git-hooks.
- Install the ESLint extension for vscode.
- Install the
@commitlint/cli
and@commitlint/config-conventional
packages. - Install the simple-git-hooks. Need to run
npx simple-git-hooks
to initialize it and update configuration."commitlint": { "extends": [ "@commitlint/config-conventional" ] }, "simple-git-hooks": { "pre-commit": "npm run precommit", "commit-msg": "npm run commitlint --edit ${1}" }
Follow the conventional versioning systems:
Use conventional commit to write commit message. The advantage is that we can use tools like changelogithub to generate changelog based on commit messages.
Adopting and consistently applying coding conventions, standards, and guidelines has been crucial for me in maintaining high-quality, maintainable codebases. Through establishing clear rules for code style, architecture, documentation, and version control, I've found that it helps me to:
- Improve code readability and consistency across my projects
- Reduce my own cognitive load when working with the code
- Minimize common errors and technical debt
- Make code reviews and collaboration more efficient
- Enable better knowledge sharing when working with others
These guidelines represent my current best practices, but I recognize they should be adapted to specific project needs. The most important aspect for me has been maintaining consistency within each project. I regularly review and update these standards as I learn new technologies and approaches.
By following these practices, I've been able to create more maintainable, efficient, and professional software projects.
- https://en.wikipedia.org/wiki/Coding_conventions
- https://github.com/ryanmcdermott/clean-code-javascript
- https://github.com/ryanmcdermott/3rs-of-software-architecture
- https://github.com/antfu/eslint-config
- https://eslint.org/
- https://conventionalcommits.org/
- https://semver.org/
- https://keepachangelog.com/
- https://rapidapi.com/learn/rest