HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🐣
프론트엔드 데브코스 4기 교육생
/
🎽
성기동팀
/
💾
자료실
/
💾
클린 코드를 위한 기동팀의 규칙
💾

클린 코드를 위한 기동팀의 규칙

Tags
규칙
Created
Oct 5, 2023 04:36 AM

eslint 설정

개인 취향을 적어보자.

  • 지성
    • import/order : import 순서 조정 (자동으로 줄바꿈 진행)
 
  • 희석
  • @typescript-eslint/naming-convention: 변수, 함수, 클래스 등의 명명 규칙을 정의합니다.
    • @typescript-eslint/naming-convention: ["error", { "format": ["camelCase"] }],
  • @typescript-eslint/no-unnecessary-type-assertion: 불필요한 타입 단언을 금지합니다.
 
  • 유진
    • 기본적인 것 말고도 참고하면 좋은 라이브러리들 목록
      • Simple-import-sort :이 라이브러리는 자동으로 임포트 순서를 정렬해줍니다
      • function-component-definition : 함수 형식을 강제로 고정할 때 사용(화살표 함수 등)
      • typescript-eslint/naming-convention : 변수 함수 인터페이스 작성 등 네이밍 컨벤션 강제화
      • react/jsx-curly-brace-presence : props로 넘겨줄때 중괄호로 넘길건지 문자열로 넘길건지 결정
      • react/no-unknown-property: 올바르지 않은 프로퍼티가 넘어왔을 때 에러처리를 해줄 수 있습니다.
      • import/newline-after-import: import 아래에 엔터칠건지 안칠건지..
      • unicorn/filename-case : 파일명을 통일시켜줍니다. (대문자로 할건지 아닌지 등)
 

지성팀 eslint~ ^^ (최종본)

prettier 설정

  • 지성
    • “semi” : 무조건false….
    • “Single Quote” : true, (’)
    • Tab width : 2 or 4 아무거나 상관없습니다!
    • Trailling Comma : none(에러가 자주 나더라구요…)
    • printWidth : 80
  • 희석
    • "semi": true,
    • "tabWidth": 4,
    • "arrowParens": "always",
    • "singleQuote": true,
  • 유진
    • Semi : false
    • Tab width 2
    • Single quote true
    • Pritnwidth 80
 

클린 코드를 위한 래퍼런스

변수명, 함수명, 일관적인 코드 작성을 위해 서로 고려했으면 하는 점

  • 지성
    • 저도 희석님과 같은 레퍼런스를 참고 했습니다
    • 컴포넌트 이름 : 대문자 + 파일과 컴포넌트 이름 겹쳤으면 좋겠습니다
      • ex) Header > Header.tsx( index.tsx ❌)
    • 자주 쓰이는 접두사
      • https://jobkae-function-naming.netlify.app/
    • 함수
      • 되도록이면 순수함수로… (사이드 이펙트는 최대한 없었으면 좋겠습니다)
      • 저도 arrow function이었으면 좋겠습니다!
      • 클린코드 문서를 좀 보다가.. 함수 파라미터로 boolean type은 지양해야한다는데 조금 더 실펴보겠습니다
  • 희석
    • 레퍼런스 : https://738.github.io/clean-code-typescript/
  • 파일명 : camelCase
  • 컴포넌트 파일명 : PascalCase
  • 변수명 : 카멜 케이스
    • ex) getUser
  • 상수명 : 대문자 + 스네이크 케이스
    • ex) SNAKE_CASE
  • 함수
    • 화살표 함수 사용
    • 함수는 한 가지의 일만 수행하기
  • 될 수 있으면 부정 조건문 사용하지 않기
    • ex) if(!data)
  • 유진
    • 함수
      • 화살표 함수가 좋아요~~
      • 함수는 되도록 리턴값이 존재하는 형식으로 작성하기!! (리턴값이 없으면 state 느낌으로 관리하도록 노력해주기)
    • api작성
      • 객체로 정리하여 api들이 한 곳에 모여 있을수 있도록 하기 (빨리 찾을 수 있음)
      • Mutation 중복되는거는 커스텀 훅으로 만들수있으면 빼기 ⇒ ‘authmutation’
    • 컴포넌트 분리 page.tsx
      • PostList.page ⇒ 300줄 넘어가는 경우가 ..
      • 포스트 제목
      • 포스트 사진
      • 포스트 글
      • 포스트 정보
      • 포스트 댓글
      • 댓글 작성하기
    • 변수명 관련
      • 희석님께서 너무 좋은 예시를 가져와주셔서
      • Javascript 네이밍(naming)과 클린 코드(clean code)
        네이밍과 클린코드의 규칙에 대해 정리했습니다.
        Javascript 네이밍(naming)과 클린 코드(clean code)
        https://www.datoybi.com/naming-clean-code-javascript/
        Javascript 네이밍(naming)과 클린 코드(clean code)
 

todo

husky 설치

기동팀 클린 코드 문서 작성

변수명

동일한 유형의 변수는 동일한 단어로 사용하기

배열 이름은 되도록 복수로 설정하기

Booleans

접두사(최대한 여기 있는거 참고하기)

잡캐헨리 함수명 접두사 살펴보기
유튜버 잡캐헨리와 함께하는 함수명 짓기
잡캐헨리 함수명 접두사 살펴보기
https://jobkae-function-naming.netlify.app/

코드 스타일

etc

  • 될 수 있으면 부정 조건문 사용하지 않기
    • ex) if(!data)

함수 작성방법

  • 클린코드 문서를 좀 보다가.. 함수 파라미터로 boolean type은 지양해야한다는데 조금 더 실펴보겠습니다
  • 화살표 함수 사용하기
  • 함수의 매개변수는 2개 이하로 작성하기
  • 하나의 함수는 한가지만 동작만 수행하기

변수 작성방

  • 파일명 : camelCase
  • 컴포넌트 파일명 : PascalCase
  • 변수명 : 카멜 케이스
    • ex) getUser
  • 상수명 : 대문자 + 스네이크 케이스
    • ex) SNAKE_CASE
 
module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:import/recommended', 'prettier', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh', 'import'], settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: true, }, }, rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], 'import/order': [ 'error', { 'newlines-between': 'always', groups: [ 'builtin', 'external', 'internal', ['parent', 'sibling'], 'index', ], pathGroups: [ { pattern: 'react*', group: 'builtin', position: 'before', }, { pattern: '@/stores/*', group: 'internal', position: 'after', }, { pattern: '@/contexts/*', group: 'internal', position: 'after', }, { pattern: '@/hooks/*', group: 'internal', position: 'after', }, { pattern: '@/components/*', group: 'internal', position: 'after', }, { pattern: '@/assets/*', group: 'internal', position: 'after', }, { pattern: '@/public/*', group: 'internal', position: 'after', }, ], pathGroupsExcludedImportTypes: [], alphabetize: { order: 'asc', }, }, ], 'import/no-unresolved': ['error', { ignore: ['.svg'] }], }, };
module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:prettier/recommended', 'plugin:storybook/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], }, };
module.exports = { env: { browser: true, es2021: true, }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:react/recommended', 'prettier', ], plugins: ['react', 'prettier', '@typescript-eslint', 'simple-import-sort'], overrides: [ { env: { node: true, }, files: ['.eslintrc.{js,cjs}'], parserOptions: { sourceType: 'script', }, }, ], parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, rules: { 'linebreak-style': ['error', require('os').EOL === '\r\n' ? 'windows' : 'unix'], 'prettier/prettier': ['error', { endOfLine: 'auto' }], 'react/no-unknown-property': ['error', { ignore: ['css'] }], 'react/prop-types': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/strict-boolean-expressions': 'off', '@typescript-eslint/explicit-function-return-type': 'off', 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', 'react/react-in-jsx-scope': 'off', 'react/jsx-curly-brace-presence': ['error', { props: 'always', children: 'always' }], 'react/function-component-definition': [ 'error', { namedComponents: 'arrow-function', unnamedComponents: 'arrow-function', }, ], '@typescript-eslint/naming-convention': [ 'error', { format: ['camelCase', 'UPPER_CASE', 'PascalCase'], selector: 'variable', leadingUnderscore: 'allow', }, { format: ['camelCase', 'PascalCase'], selector: 'function', }, { format: ['PascalCase'], selector: 'interface', }, { format: ['PascalCase'], selector: 'typeAlias', }, ], }, parser: '@typescript-eslint/parser', }
- `function-component-definition` : 화살표 함수 - `typescript-eslint/naming-convention` : 변수 함수 인터페이스 작성 등 네이밍 컨벤션 강제화 - `react/jsx-curly-brace-presence` : props로 넘겨줄때 중괄호(확정❗️) - `react/no-unknown-property`: 올바르지 않은 프로퍼티가 넘어왔을 때 에러처리를 해줄 수 있습니다. - `import/newline-after-import`: import 아래에 엔터칠건지 안칠건지.. - `import/order` : import 순서 조정 (자동으로 줄바꿈 진행) - `unicorn/filename-case` : 파일명을 통일시켜줍니다. (대문자로 할건지 아닌지 등) - `@typescript-eslint/no-unnecessary-type-assertion`**: 불필요한 타입 단언을 경고❗️합니다
//기본적인 세팅 'prettier/prettier': ['error', { endOfLine: 'auto' }], 'react/no-unknown-property': ['error', { ignore: ['css'] }], 'react/prop-types': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/strict-boolean-expressions': 'off', '@typescript-eslint/explicit-function-return-type': 'off', ---- '@typescript-eslint/naming-convention': [ 'error', { format: ['camelCase', 'UPPER_CASE', 'PascalCase'], selector: 'variable', leadingUnderscore: 'allow', }, { format: ['camelCase', 'PascalCase'], selector: 'function', }, { format: ['PascalCase'], selector: 'interface', }, { format: ['PascalCase'], selector: 'typeAlias', }, 'react/jsx-curly-brace-presence': ['error', { props: 'always', children: 'always' }], { "import/order": [ "error", { "groups": [ "type", "builtin", "external", "internal", "parent", "sibling", "index", "unknown" ], "pathGroups": [ { "pattern": "react*", "group": "external", "position": "before" }, { "pattern": "@hooks/*", "group": "internal", "position": "after" }, { "pattern": "@pages/*", "group": "internal", "position": "after" }, { "pattern": "@components/*", "group": "internal", "position": "after" } ], "pathGroupsExcludedImportTypes": ["@tanstack*"], "alphabetize": { "order": "asc" } } ] }, "unicorn/filename-case": [ "error", { "cases": { "camelCase": true, "pascalCase": true } } ], - `@typescript-eslint/no-unnecessary-type-assertion`**: 불필요한 타입 단언을 금지합니다.
semi : false "trailling Comma" : none(에러가 자주 나더라구요…) "arrowParens": "always", "singleQuote": true, "tab width" : 2, "printwidth" : 80
{ // prettier전체 옵션이라고하는데 혹시 빼먹은거 있으면 말씀해주세요! "arrowParens": "avoid", // 화살표 함수 괄호 사용 방식 "bracketSpacing": false, // 객체 리터럴에서 괄호에 공백 삽입 여부 "endOfLine": "auto", // EoF 방식, OS별로 처리 방식이 다름 "htmlWhitespaceSensitivity": "css", // HTML 공백 감도 설정 "jsxBracketSameLine": false, // JSX의 마지막 `>`를 다음 줄로 내릴지 여부 "jsxSingleQuote": false, // JSX에 singe 쿼테이션 사용 여부 "printWidth": 80, // 줄 바꿈 할 폭 길이 "proseWrap": "preserve", // markdown 텍스트의 줄바꿈 방식 (v1.8.2) "quoteProps": "as-needed" // 객체 속성에 쿼테이션 적용 방식 "semi": true, // 세미콜론 사용 여부 "singleQuote": true, // single 쿼테이션 사용 여부 "tabWidth": 2, // 탭 너비 "trailingComma": "all", // 여러 줄을 사용할 때, 후행 콤마 사용 방식 "useTabs": false, // 탭 사용 여부 "vueIndentScriptAndStyle": true, // Vue 파일의 script와 style 태그의 들여쓰기 여부 (v1.19.0) "parser": '', // 사용할 parser를 지정, 자동으로 지정됨 "filepath": '', // parser를 유추할 수 있는 파일을 지정 "rangeStart": 0, // 포맷팅을 부분 적용할 파일의 시작 라인 지정 "rangeEnd": Infinity, // 포맷팅 부분 적용할 파일의 끝 라인 지정, "requirePragma": false, // 파일 상단에 미리 정의된 주석을 작성하고 Pragma로 포맷팅 사용 여부 지정 (v1.8.0) "insertPragma": false, // 미리 정의된 @format marker의 사용 여부 (v1.8.0) "overrides": [ { "files": "*.json", "options": { "printWidth": 200 } } ], // 특정 파일별로 옵션을 다르게 지정함, ESLint 방식 사용 }
//bad function getUserInfo(): User; function getUserDetails(): User; function getUserData(): User;
//good function getUser(): User; // 여러 개 가져올 때 vs 다 가져올 때 const = getusers() vs const getAllUsers() => {...}
// bad const fruit = ['apple', 'banana', 'cucumber']; // okay const fruitArr = ['apple', 'banana', 'cucumber']; // good const fruits = ['apple', 'banana', 'cucumber']; // great const fruitNames = ['apple', 'banana', 'cucumber']; const fruits = [ { name: 'apple', genus: 'malus' }, { name: 'banana', genus: 'musa' }, { name: 'cucumber', genus: 'cucumis' }, ];
// bad const open = true; const write = true; const fruit = true; const equal = true; const visible = true; // good const isOpen = true; const canWrite = true; const hasFruit = true; const areEqual = true; const isVisible = trueBODY: $PR_BODY" ISSUE_NUMBER=$(echo $PR_BODY | grep -oE "close #[0-9]+" | tr -d 'close #') echo "ISSUE_NUMBER: $ISSUE_NUMBER" if [[ ! -z "$ISSUE_NUMBER" ]]; then curl -s -H "Authorization: token ${{ secrets.ACTION_TOKEN }}" -X PATCH "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER" -d '{"state": "closed"}' fi shell: bash
name: Close associated issue on: pull_request: branches: - dev types: - closed jobs: close-issue: runs-on: ubuntu-latest steps: - name: Close associated issue run: | PR_NUMBER=${{ github.event.pull_request.number }} PR_URL="https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" echo "PR_URL: $PR_URL" PR_BODY=$(curl -s -H "Authorization: token ${{ secrets.ACTION_TOKEN }}" $PR_URL | jq -r '.body') echo "PR_BODY: $PR_BODY" ISSUE_NUMBER=$(echo $PR_BODY | grep -oE "close #[0-9]+" | tr -d 'close #') echo "ISSUE_NUMBER: $ISSUE_NUMBER" if [[ ! -z "$ISSUE_NUMBER" ]]; then curl -s -H "Authorization: token ${{ secrets.ACTION_TOKEN }}" -X PATCH "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER" -d '{"state": "closed"}' fi shell: bash