diff --git a/README.md b/README.md index b9565d0..db8cf3e 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ Made by [Codempire](http://codempire.io/) 1. [Javascript](docs/javascript.md) 2. [React](docs/react/README.md) -3. [Typescript](docs/typings.md) +3. [Typescript](docs/typescript/README.md) 4. [Development](docs/development.md) 5. [Tools](docs/tools.md) 6. [Git Flow](docs/git.md) +7. [Clear code](docs/clear-code/README.md) ## Contact Us diff --git a/assets/clear-code/end-form.png b/assets/clear-code/end-form.png new file mode 100644 index 0000000..3437040 Binary files /dev/null and b/assets/clear-code/end-form.png differ diff --git a/assets/clear-code/form-type.png b/assets/clear-code/form-type.png new file mode 100644 index 0000000..d8a9349 Binary files /dev/null and b/assets/clear-code/form-type.png differ diff --git a/assets/clear-code/input-field-refactoring.png b/assets/clear-code/input-field-refactoring.png new file mode 100644 index 0000000..0ed89fe Binary files /dev/null and b/assets/clear-code/input-field-refactoring.png differ diff --git a/assets/clear-code/start-form.png b/assets/clear-code/start-form.png new file mode 100644 index 0000000..1981014 Binary files /dev/null and b/assets/clear-code/start-form.png differ diff --git a/assets/clear-code/update-field-function.png b/assets/clear-code/update-field-function.png new file mode 100644 index 0000000..a38a4e5 Binary files /dev/null and b/assets/clear-code/update-field-function.png differ diff --git a/assets/work-partial-function-example.gif b/assets/work-partial-function-example.gif new file mode 100644 index 0000000..abcdec2 Binary files /dev/null and b/assets/work-partial-function-example.gif differ diff --git a/docs/clear-code/README.md b/docs/clear-code/README.md new file mode 100644 index 0000000..cd046ee --- /dev/null +++ b/docs/clear-code/README.md @@ -0,0 +1,10 @@ +# React Ecosystem + +[Return to Table of Contents](../../README.md). + +> Tutorial with practices of create clear + +## Table of Contents + +1. [Main rules](clear-code.md) +2. [Pattern](patter.md) diff --git a/docs/clear-code/clear-code.md b/docs/clear-code/clear-code.md new file mode 100644 index 0000000..c440709 --- /dev/null +++ b/docs/clear-code/clear-code.md @@ -0,0 +1,469 @@ +# Clear code + +[Return to Table of Contents](../../README.md). + +[Return to Clear code](./README.md). + +## Table of Contents 🚀 + +- [**Variables**](#Variables) +- [**Function**](#Function) +- [**Formatting**](#Formatting) + +## Variables + +### Use meaningful and meaningful names + +👎**Bad:** + +```ts +const d = moment().format("YYYY/MM/DD"); +``` + +👍**Good:** + +```ts +const currentDate = moment().format("YYYY/MM/DD"); +``` + +### Don't use magic number + +👎**Bad:** + +```ts +// What the heck is 86400000 for? +setTimeout(blastOff, 86400000); +``` + +👍**Good:** + +```ts +// Declare them as capitalized named constants. +const MILLISECONDS_IN_A_DAY = 86400000; + +setTimeout(blastOff, MILLISECONDS_IN_A_DAY); +``` + +### Use explanatory variables + +👎**Bad:** + +```ts +const address = "Some address"; +const cityZipCodeRegex = /^\d{5}-\d{4}|\d{5}|[A-Z]\d[A-Z] \d[A-Z]\d$/; +saveCityZipCode( + address.match(cityZipCodeRegex)[1], + address.match(cityZipCodeRegex)[2] +); +``` + +👍**Good:** + +```ts +const address = "Some address"; +const cityZipCodeRegex = /^\d{5}-\d{4}|\d{5}|[A-Z]\d[A-Z] \d[A-Z]\d$/; +const [_, city, zipCode] = address.match(cityZipCodeRegex) || []; +saveCityZipCode(city, zipCode); +``` + +### Avoid intuitive mappings + +👎**Bad:** + +```ts +const locations = ["Austin", "New York", "San Francisco"]; +locations.forEach((l) => { + doSomething(l); +}); +``` + +👍**Good:** + +```ts +const locations = ["Austin", "New York", "San Francisco"]; +locations.forEach((location) => { + doSomething(location); +}); +``` + +### Don't add unnecessary context + +👎**Bad:** + +```ts +const Job = { + jobCompany: "codempire", + jobStack: "Front-end developer", + jobTime: 8, +}; + +const changeStack = (job) => { + job.jobStack = "FullStack"; +}; +``` + +👍**Good:** + +```ts +const Job = { + company: "codempire", + stack: "Front-end developer", + time: 8, +}; + +const changeStack = (job) => { + job.stack = "FullStack"; +}; +``` + +### Use default value in function + +👎**Bad:** + +```ts +const createFullName = (firstName?: string, lastName?: string) => { + const fName = firstName || "Name"; + // ... +}; +``` + +👍**Good:** + +```ts +const createFullName = (firstName = "Name", lastName = "Surname") => { + // ... +}; +``` + +## Function + +### The function should complete only one task. + +👎**Bad:** + +```ts +const getActiveClientEmails = (clients: IClient[]) => { + clients.forEach((client) => { + const clientRecord = database.lookup(client); + if (clientRecord.isActive()) { + email(client); + } + }); +}; +``` + +👍**Good:** + +```ts +const getActiveClientEmails = (clients: IClient[]) => + clients.filter(isActiveClient).forEach(email); + +const isActiveClient = (client: IClient) => { + const record = database.lookup(client); + return record.isActive(); +}; +``` + +### Names should describe what the function does + +👎**Bad:** + +```ts +function addToDate(date: Date, month: number) { + // ... +} + +const date = new Date(); +// It's hard to to tell from the function name what is added +addToDate(date, 1); +``` + +👍**Good:** + +```ts +function addMonthToDate(month: number, date: Date) { + // ... +} + +const date = new Date(); +addMonthToDate(1, date); +``` + +### Don't use the flag as a parameter + +👎**Bad:** + +```ts +const createFile = (name: string, temp: boolean) => { + temp ? fs.create(`./temp/${name}`) : fs.create(name); +}; +``` + +👍**Good:** + +```ts +const createFile = (name: string) => { + fs.create(name); +}; + +const createTempFile = (name: string) => { + createFile(`./temp/${name}`); +}; +``` + +### Avoid negative conditions + +👎**Bad:** + +```ts +function isDOM(node: any): node is HTMLElement { + // ... +} + +if (!isDOM(node)) { + // ... +} +``` + +👍**Good:** + +```ts +function isNotDOM(node: any): boolean { + // ... +} + +if (isNotDOM(node)) { + // ... +} +``` + +### The ideal number of arguments for functions is two or less + +👎**Bad:** + +```ts +const createMenu = ( + title: string, + body: string, + buttonText: string, + cancellable: string +) => { + // ... +}; +``` + +👍**Good:** + +```ts +interface ICreateMenuParameters { + title: string; + body: string; + buttonText: string; + cancellable: string; +} + +const createMenu = (parameters: ICreateMenuParameters) => { + const { title, body, buttonText, cancellable } = parameters; + // ... +}; + +createMenu({ + title: "Foo", + body: "Bar", + buttonText: "Baz", + cancellable: true, +}); +``` + +### Use currying if some variable is identical + +👎**Bad:** + +```ts +const createTemplate = (folderName: string, fileName: string) => + `/${foldername}/${fileName}`; + +createTemplate("react", "index"); +createTemplate("react", "state"); +``` + +👍**Good:** + +```ts +const createTemplate = (folderName: string) => (fileName: string) => + `/${foldername}/${fileName}`; + +const createReactTemplate = createTemplate("react"); + +createReactTemplate("index"); +createReactTemplate("state"); +``` + +### Global functions are not overridden + +👎**Bad:** + +```ts +Array.prototype.diff = function diff(comparisonArray) { + const hash = new Set(comparisonArray); + return this.filter((elem) => !hash.has(elem)); +}; +``` + +👍**Good:** + +```ts +class SuperArray extends Array { + diff(comparisonArray) { + const hash = new Set(comparisonArray); + return this.filter((elem) => !hash.has(elem)); + } +} +``` + +### Delete unusable code + +👎**Bad:** + +```ts +// Bad +function oldRequestModule(url: string) { + // ... +} + +function newRequestModule(url: string) { + // ... +} + +const req = newRequestModule; +inventoryTracker("apples", req, "test-url"); +``` + +👍**Good:** + +```ts +function newRequestModule(url: string) { + // ... +} + +const req = newRequestModule; +inventoryTracker("apples", req, "test-url"); +``` + +## Formatting + +### Comment only code containing complex business logic + +👎**Bad:** + +```ts +function hashIt(data: string) { + // The hash + let hash = 0; + + // Length of string + const length = data.length; + + // Loop through every character in data + for (let i = 0; i < length; i++) { + // Get character code. + const char = data.charCodeAt(i); + // Make the hash + hash = (hash << 5) - hash + char; + // Convert to 32-bit integer + hash &= hash; + } +} +``` + +👍**Good:** + +```ts +function hashIt(data: string) { + let hash = 0; + const length = data.length; + + for (let i = 0; i < length; i++) { + const char = data.charCodeAt(i); + hash = (hash << 5) - hash + char; + + // Convert to 32-bit integer + hash &= hash; + } +} +``` + +### Don't comment out unnecessary code + +👎**Bad:** + +```ts +doStuff(); +// doOtherStuff(); +// doSomeMoreStuff(); +// doSoMuchStuff(); +``` + +👍**Good:** + +```ts +doStuff(); +``` + +### Never keep a comment journal + +👎**Bad:** + +```ts +/** + * 2016-12-20: Removed monads, didn't understand them (RM) + * 2016-10-01: Improved using special monads (JP) + * 2016-02-03: Removed type-checking (LI) + * 2015-03-14: Added combine with type-checking (JR) + */ +function combine(a: number, b: number) { + return a + b; +} +``` + +👍**Good:** + +```ts +function combine(a: number, b: number) { + return a + b; +} +``` + +### Don't use position markers + +👎**Bad:** + +```ts +//////////////////////////////////////////////////////////////////////////////// +// Scope Model Instantiation +//////////////////////////////////////////////////////////////////////////////// +$scope.model = { + menu: "foo", + nav: "bar", +}; + +//////////////////////////////////////////////////////////////////////////////// +// Action setup +//////////////////////////////////////////////////////////////////////////////// +const actions = function () { + // ... +}; +``` + +👍**Good:** + +```ts +$scope.model = { + menu: "foo", + nav: "bar", +}; + +const actions = function () { + // ... +}; +``` diff --git a/docs/clear-code/patter.md b/docs/clear-code/patter.md new file mode 100644 index 0000000..35de631 --- /dev/null +++ b/docs/clear-code/patter.md @@ -0,0 +1,48 @@ +# Patterns + +[Return to Table of Contents](../../README.md). + +[Return to Clear code](./README.md). + +**KISS - "Keep the code simple and stupid."** + +> _I think you are familiar with the concept of simple code. But what does "stupid" code mean? In my opinion, this is code that solves the problem using a minimum number of abstractions, while the nesting of these abstractions in each other is also minimal._ + +**YAGNI - "You ain't gonna need it"** + +> _Code must be able to do what for what it created. We do not create any functionality that may be needed later, or that makes the application better, in our opinion. We only do require for the implementation of the task._ + +**DRY - "don't repeat yourself."** + +> _The principle seems simple, but it has a catch: to get rid of duplicate code, you need to create abstractions. If these abstractions are not good enough, we violate the KISS principle._ +> +> _The main task of this pattern is to create code that simplifies your code for updates and read. Before creating an abstract function or component simple way is to create some simple solution and after that change this code to more abstract._ + +### This is small examples with form. + +#### You can look at how all pattern work + +Now I am creating some simple form from login to site. + +![Form typing](../../assets/clear-code/form-type.png) +![Start form](../../assets/clear-code/start-form.png) + +How you can see some parts are identical in this code. We can move this code to component and use. + +![Input fields](../../assets/clear-code/input-field-refactoring.png) + +Reading got easier. The name of the component corresponds to the problem it solves. The purpose of the element is evident. There is less code. So we are heading in the right direction! + +The **next step** is to create a new function that updates the field. Yes, I agree that this function is not much stupid, but this function help simplify code and complete patter DRY. + +![Input fields](../../assets/clear-code/update-field-function.png) + +This function currying, and in the first function, they get the name of the field. This function returns another function, which catches in parameter new value for a field in the state. + +You can change this function and component, but in this example, I don't need to add some additional functionality, so I use pattern YAGNI. + +After all those manipulation complete, we get a form like this. + +![Input fields](../../assets/clear-code/end-form.png) + +At the end of this example, you can see how the code changed and simplified. diff --git a/docs/react/README.md b/docs/react/README.md index 7180740..1706b1f 100644 --- a/docs/react/README.md +++ b/docs/react/README.md @@ -1,6 +1,6 @@ # React Ecosystem -[Return to Table of Contents](../README.md). +[Return to Table of Contents](../../README.md). > There're our rules that we are using when working with react library and it's ecosystem diff --git a/docs/typescript/README.md b/docs/typescript/README.md new file mode 100644 index 0000000..ccdc1fd --- /dev/null +++ b/docs/typescript/README.md @@ -0,0 +1,10 @@ +# React Ecosystem + +[Return to Table of Contents](../../README.md). + +> Tutorial with TypeScript code example + +## Table of Contents + +1. [Typings](typings.md) +2. [Partial](partial.md) diff --git a/docs/typescript/partial.md b/docs/typescript/partial.md new file mode 100644 index 0000000..bf50c2e --- /dev/null +++ b/docs/typescript/partial.md @@ -0,0 +1,88 @@ +# Partial + +[Return to Table of Contents](../../README.md). + +[Return to Typescript](./README.md). + +#### `Partial ` - constructs a type with all properties of `T` set to optional. + +**How it work?** + +```ts +interface A { + title: string; + subtitle: string; +} + +type PartialA = Partial; + +interface B { + title?: string; + subtitle?: string; +} + +A type PartialA and interface B is equal, but the type has shorter. +``` + +**Example of use** + +I show you little examples of code from the Redux reducer and action creator. + +```ts +interface IReducer { + page: string; + modal: string; +} + +export const Actions = { + setModal: (modal?: string) => ({ type: SET_MODAL, payload: modal }), + setPage: (page: string) => ({ type: SET_PAGE, payload: page }), +}; + +export const reducer = (state = INIT_REDUX, action: TActions): IReducer => { + switch (action.type) { + case SET_MODAL: { + return { ...state, modal: action.payload }; + } + + case SET_PAGE: { + return { ...state, page: action.payload }; + } + + default: { + return state ?? INIT_REDUX; + } + } +}; +``` + +Suppose the reducer contains two or three fields, this code work. But if you have ten tracks and use code like this, you start to get some problem. If you need to update two areas one time, you think to create one more action creator or change the function or use two action creator in one place. The last variation can generate an error "Maximum update depth exceeded error" when you call one-by-one action creator from one reducer. + +The easy way was using in this situation code like this. + +```ts +export const Actions = { + updateFields: (fields: Partial) => ({ + type: UPDATE_FIELDS, + payload: fields, + }), +}; + +export const reducer = (state = INIT_REDUX, action: TActions): IReducer => { + switch (action.type) { + case UPDATE_FIELDS: { + return { ...state, ...action.payload }; + } + + default: { + return state ?? INIT_REDUX; + } + } +}; +``` + +_How can you use this function in the project?_ + +![Work Partial function example](../../assets/work-partial-function-example.gif) + +You can in call action update how much fields how much you want. But if you have hard logic with change/update fields prettier move this logic to the reducer and use new action creator. diff --git a/docs/typings.md b/docs/typescript/typings.md similarity index 83% rename from docs/typings.md rename to docs/typescript/typings.md index aa768fc..97cabbc 100644 --- a/docs/typings.md +++ b/docs/typescript/typings.md @@ -1,6 +1,8 @@ # Typings -[Return to Table of Contents](../README.md) +[Return to Table of Contents](../../README.md) + +[Return to Typescript](./README.md). ## 1. Types `any`, `array` and `object` not allowed @@ -71,7 +73,7 @@ interface IData { } ``` -## 7. Use `?` when declaring noncertain types +## 7. Use `?` when declaring uncertain types > Instead of @@ -132,10 +134,20 @@ interface IService { } ``` -## 10. When using React always add typing for React Children +## 10. When using React component use interface FC ```typescript -interface IComponent { - children: ReactNode | ReactChild | ReactElement; +import React, { FC } from 'react'; + +interface IComponentProps { + parameter: string; +} + +const Component: FC = (props) => { + const {parameter, children} = props; + + return ( + //... + ) } ```