All teams work differently, a team's style should represent the deliberation and negotiation of each coding aspect by the members of that team.
- These are not immutable rules, things change, evolve and serve different purposes in different stages of development and experience.
- All conventions are right as long as people using them agree on their meaning and form.
The following is inspired from:
-
Continuous refactoring: Developers read much more code than they write. Don't stop at your fist draft while also not obsessing on getting it 100% right.
-
Read the room: Functional programming, or any one paradigm, are not magic bullets. Figure out when to use what. When building primitives for example, it makes sense to be more conscious about performance and adopt a more imperative style - still being aware of immutability but not spreading all the object all the time.
-
One way execution flow: Make code execution flow top -> down and left -> right. The more you go back, inside or outside the more you switch context and zoom level. These switches reset the readers mental parsing "state" making the code's meaning harder to transmit and more prone to mistakes.
-
Let machines do their job: If there's a linting rule that can verify or enforce a conventions, use it.
80 characters, yes, just like our grand parents with punch cards.
It turns out that there is an optimal line size for legibility, between 40 and 80 - taking into account eye/head movement, focus etc. This is true for printed books and also code.
At the beginning of every new line the reader is focused, but this focus gradually wears off over the duration of the line.
"Typographie", E. Ruder
80-characters line length limit in 2017 (and later) and Readability: the Optimal Line Length are good intros on the topic.
The principle of "One Way Data Flow" can also be applied to component imports/dependencies.
-
The project folder structure should resemble more a Polytree, not the required Directed acyclic graph
-
Use Hierarchical model–view–controller when creating pages, containers or multi part entities
▾ HMVC/ ▾ todos/ controller model view ▾ comments/ controller model view ▾ MVC/ ▾ controllers/ todos comments ▾ models/ todos comments ▾ views/ todos comments
-
A child should never import something from it's parents or it's siblings, it can only have access to it's children
-
If a child needs something from it's parent, that piece of data should be handed down through props (data, functions or other Components)
- Main:
main
- PR:
feat|fix|docs/{task-id}/few-word-summary
Use Conventional Commits.
feat(ui): allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files
A functions is a thing that does something, performs an action, if it does
something, it's a verb. The stronger and less noise around the verb, the
better. renameFile
better than fileRename
or doRenameFile
.
Functions start with a verb, optionally followed by the noun and/or an adjective:
const rename = () => { ... }
const renameFile = () => { ... }
const renameMarkdownFiles = () => { ... }
All functions ever written are a specialized form of one of the following verbs:
Create
: "build", "initialize", "construct"Read
: "find", "get", "check"Update
: "change", "rename", "map", "filter"Delete
: "remove"
Use the verb in it's basic form
(infinitive). rename
, not
renaming
or doRename
or renames
.
Use a name intentionally and strengthen it's meaning by constantly implementing it with the same behavior, for ex:
getTodo
throws if not found,findTodo
returns nullupdateComment
implies persistence (API or DB interaction),changeCommentText
refers to local state changessum
adds items of an array,sumBy
adds the field of an object array by providing an "extract" function
Prefix predicate functions, functions that return a boolean, with a verbs
that imply truth seeking - check
, verify
, decide
, determine
- followed
by a question:
const isToday = checkIsToday(new Date())
Boolean variables, including React props, are prefixed with one of the
following: can
, should
, is
, has
, was
const isAllowed = false
const shouldRender = true
Always UPPER_CASE
.
const LANGUAGES = ["EN-us", "NL-nl", "NL-be"]
Function props
are prefixed with on
and phrased as an event:
onInputChange
,onFormSubmit
etc.
Functions passed as props for reacting to an event are prefixed with
handle
, followed by the main verb, optionally can use a noun
and/or a adjective:
handleUpdateUser
,handleSwitchTab
,handleDisableWorkshopUser
etc.
Example: Persist Users' language preference
const handleUpdateUserLocale = useCallback(
input => update({ locale: input }),
[update]
)
...
<Sidebar onLocaleChange={handleUpdateUserLocale} />
- Append
Type
to the name of the thing it's typing and CamelCase it.
type InputUIPropsType = {
type: "password";
value: string;
hasShowUnmasked: boolean;
} | {
type: "text";
value: string;
}
- Separate types from the "actual" code. Writing them inline makes it harder to distinguish actionable code from what fundamentally is compiler oriented code
// separate
const InputUI: FC<InputUIPropsType> = ({ value, type, hasShowUnmasked }) => {
...
}
// inlined
const InputUI = (
props:
| {
type: 'password';
value: string;
hasShowUnmasked: boolean;
}
| {
type: 'text';
value: string;
}
) => {
...
};
-
input
andoutput
const sum = (input = []) => { let output = 0 for (let index = 0, length = input.length; index < length; index++) { output = output + input[index] } return output }
-
input
,item
andaccumulator
const sum = (input = []) => input.reduce( (accumulator, item) => accumulator + item, 0 )
-
needle
,heystack
andindex
const checkExistsWithValue = needle => (heystack = []) => { for (let index = 0; index < heystack.length; index++) { if (item === heystack[index]) { return true } } return false }
-
When using "functional primitives" like
map
orreduce
, useitem
,array
andaccumulator
-
When writing CRUD like functions, use
id
anddata
const handleCreateTodo = useCallback( data => POST("/todos", { body: data }), [] ) const handleUpdateTodo = useCallback( (id, data) => PATCH(`/todos/${id}`, { body: data }), [] )
-
properties
andoptions
, notargs
oropts
orprops
. Aligns with React ecosystem, use it also when writing other functions that require configuring
- Tag all the essential parts of the component with a
data-testid
. It is the state and composition of these parts that determine the behavior of the component. - Check the component's configuration without coupling the test to HTML or CSS, making it less brittle. If I need to enforce a certain HTML tag, you can check the attributes of the element in question.
- Using multiple selector functions (
queryByRole
,findByText
,getAllByRole
etc) creates noise and adds complexity in a space where everything should be as dumb/simple as possible - the subject matter is the component you're currently testing, not how fancy the API of the test library or the test runner are - hence I would use fewer different query functions and test what the state of the a part is.