생각 공유개인이 생각한 컨벤션코드를 보고 느껴지는 문제점개인이 생각하는 리팩터링 방향컨벤션폴더 구조타입 지정CSS 프로퍼티 순서스타일드 컴포넌트Import 순서이벤트 핸들러 함수함수 정의Container? Wrapper?리팩토링 일정PR RulePR 단위리뷰 기간Pn 룰Comment Resolve 기준
생각 공유
개인이 생각한 컨벤션
준혁
[ 함수 선언 ]
- 컴포넌트를 포함한 함수는 함수 선언문으로 선언합니다. 단,
*.stories.tsx
는 이를 무시할 수 있습니다.
- 리액트 컴포넌트 내부에 활용되는 함수는 분리할 수 있다면 외부에 함수 선언문으로 작성하고, 분리할 수 없다면 내부에 함수 표현식으로 작성할 수 있습니다.
// bad const Component: React.FC = () => { return <div /> } // good function Component () { const [state, setState] = useState(false) const toggle = () => setState((prev) => !prev) return <div /> }
[ 인터페이스 및 타입 ]
- 불가피하게 Type을 사용할 수밖에 없는 경우를 제외하고는, Interface를 사용합니다.
- 서버로부터 내려오는 API의 인터페이스를 정의할 때는
QuizApi
형식으로 뒤에 postfix로 API를 사용합니다. - 클라이언트에서 커스텀 데이터 인터페이스를 정의할 때는
QuizModel
형식으로 뒤에 postfix로 Model을 사용합니다.
- 기본 인터페이스를 정의하고, 해당 인터페이스를 부분적으로 갖다가 사용할 때에는
Pick<T, P>
또는Partial<T>
등 타입스크립트의 타입을 적극적으로 사용합니다.
- Interface와 Type은 PascalCase를 사용합니다.
- Type은 Type임을 표시할 수 있도록 뒤에 Type 또는 Types postfix를 붙입니다.
// bad type someType = { color: string; } function Component({color}: sometype) {} // good interface Props { color: string } type ReturnTypes = [boolean, () => void] function Component({color}: ISomeInterface): ReturnTypes { const [state, setState] = useState(false) const toggle = useCallback(() => { setState((prev) => !prev) }, []) return [state, toggle] } // interface interface QuizModel { //... } type QuizApi = Pick<QuizModel, 'id' | 'answerType' | 'createAt' | 'updatedAt'>
- 인터페이스가 전역으로 사용될 여지가 있을 경우에는 types 또는 interface 폴더에 넣어서 사용할 수 있습니다.
- 그 외 단순 컴포넌트의 Props state를 정의하거나 전역적으로 사용되는 타입이나 인터페이스가 아니라면 해당 함수가 선언된 위치에서 인터페이스 정의 후 export 할 수 있습니다.
[ 폴더 구조 ]
현재 체퀴즈의 폴더 구조는 다음과 같습니다.
src |-pages |-components |-hooks |-api |-common |-containers |-contexts |-pages |-routes //...
문제점
- 컨벤션에 맞지 않는 폴더명이 있다.
- ex)
api
는 왜 복수형으로 사용하지 않은 걸까요?
- Components 폴더 내부가 너무 복잡하다.
- 해당 폴더 내에는 도메인과 완벽히 분리된, 크기가 작은 컴포넌트부터 도메인에 완전히 종속된 크기가 큰 컴포넌트들이 존재한다.
- 사용하지 않는 폴더
commons
,containers
는 사용하는 사람만 사용하고, 대부분은 사용하지 않는다.
따라서, 앞으로 원할한 유지 보수를 위해 폴더 구조를 개편해야 할 필요가 있다.
해결 방안?
현재 가장 문제가 되는 부분과, 앞으로 가장 문제가 될 부분은 다음과 같다.
- 컴포넌트 폴더 내부에 도메인에 의존하는 컴포넌트와 그렇지 않은 컴포넌트들이 혼재된다.
- 미래에 hooks 패턴으로 로직과 UI를 분리하게될 경우, 컴포넌트와 마찬가지로 비슷한 문제를 야기할 가능성이 크다.
따라서, 현재 폴더 구조를 도메인 단위로 나눠서 작성하는 것을 제안한다.
현재 우리 프로젝트는 크게 4개의 도메인을 가지고 있다.
- auth
- quiz
- user (ranking, info)
- shared (utils, hooks)
따라서 완벽하게 재사용할 수 있을 만큼 작은 단위의 components 및 hooks, utils 함수들은 모두 shared 폴더 내부에서 정의하고 사용하는 것이 어떨까?
그리고 각 도메인 폴더에서는 해당 도메인에서 사용될 components 및 hooks를 사용하는 것이다.
폴더 구조는 다음과 같게 될 것이다.
src |--auth |--quiz --|--apis --|--components --|--hooks --|--pages |--user |--shared --|--components --|--hooks --|--utils
- 폴더 구조가 더 중첩될 수 있는 문제점이 있는데, 해당 문제를 해결하기 위하여 절대 경로를 정의해두면 어느 정도 해결될 것이라 생각한다.
[ 파일 구조 ]
하나의 폴더 내부에는 다음의 파일 구조를 가질 수 있습니다.
index.ts // export Component.tsx // 실제 로직 또는 UI가 담긴 파일 helpers.ts // hooks 또는 Component로부터 분리 가능하지만, 도메인에 의존하는 함수들 Component.styles.ts or styles.ts // 스타일 파일
[ 스타일 ]
- 스타일은 8px rule를 지켜서 정의합니다.
[ import ]
- import 순서는 다음의 규칙을 따릅니다.
- react 관련 import
- 라이브러리 관련 import
- 모듈 or 커스텀 Hook 관련 import
- 스타일 import
// react 관련 import React, { useEffect, useState, useContext, useCallback } from 'react'; // libraries import axios from 'axios'; import dayjs from 'dayjs'; // modules import CustomComponent from 'components/CustomComponent'; // styles import * as S from './styles'; import 'global.module.scss';
[ CSS 작성 순서 ]
css는 다음의 순서에 맞게 작성합니다. → mozilla css 작성 순서를 딸
1. display: 객체의 노출여부/표현방식 2. list-style 3. position 4. float 5. clear 6. width / height 7. padding / margin 8. border / background 9. color / font 10. text-decoration 11. text-align / vertical-align 12. white-space 13. other text 14. content
[ Lint ]
eslint
현재 체퀴즈에서 사용하는 lint rules는 다음과 같다.
기존
{ "env": { "browser": true, "node": true, "es6": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "airbnb", "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:prettier/recommended" ], "plugins": [ "prettier" ], "rules": { "linebreak-style": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "react/jsx-filename-extension": [ 1, { "extensions": [ ".js", ".jsx", "tsx" ] } ], "import/no-unresolved": "off", "react/react-in-jsx-scope": "off", "react/jsx-props-no-spreading": "off", "react/require-default-props": "off", "import/no-extraneous-dependencies": [ "error", { "devDependencies": true } ], "import/extensions": "off", "@typescript-eslint/no-non-null-assertion": "off", "prettier/prettier": ["error", { "endOfLine": "auto" }], "no-underscore-dangle": "off", "import/prefer-default-export": "off", }, "overrides": [ { "files": [ "*.ts", "*.tsx" ], "rules": { "no-undef": "off", "no-unused-vars": "off" } }, { "files": [ "*.config.js" ], "rules": { "@typescript-eslint/no-var-requires": "off" } } ], "parser": "@typescript-eslint/parser" }
아래에 굉장히 많은 rules이 override 되어 있는데, 그 이유는
airbnb
rule만 설치하고 TypeScript에서 사용할 eslint-config-airbnb-typescript
rule을 설치하지 않았기 때문이다.(…) 따라서 해당 config plugin을 설치한다면, 다음과 같이 eslint rules이 간단해진다. (아마도) 코드 상의 rule 변화는 없을 것이라 예상한다.개정
{ "env": { "browser": true, "node": true, "es6": true }, "parser": "@typescript-eslint/parser", "parserOptions": { "tsconfigRootDir": "./", "project": ["./tsconfig.json"] }, "plugins": ["prettier", "@typescript-eslint"], "extends": [ "plugin:@typescript-eslint/recommended", "airbnb", "airbnb-typescript", "plugin:prettier/recommended" ], "rules": { "react/jsx-props-no-spreading": "off", "react/require-default-props": "off", "import/prefer-default-export": "off", "react/default-props-match-prop-types": "off" }, "overrides": [ { "files": ["*.stories.tsx", "styles.ts", "*.styles.ts"], "rules": { "react/function-component-definition": "off" } } ] }
prettier
Windows 환경과 linux, mac의 환경을 동일하게 맞추기 위하여, endOfLine을 ‘auto’ 에서 ‘lf’로 통일하는 것을 제안한다. → 대부분 Linux 사용자 및 mac 사용자와 관련이 없다.
개정
{ "singleQuote": true, "semi": true, "useTabs": false, "tabWidth": 2, "trailingComma": "all", "printWidth": 80, }
추가
- .editorconfig 추가
editorconfig
vscode extension은 개발자가 잊기 쉬운 마지막 줄 자동 추가 및 자동으로 endOfLine 처리, tabSize 등을 맞춰주는 역할을 한다. 이 부분은 특히 windows 사용자를 위하여 꼭 추가했으면 좋겠다.rules
// .editorconfig root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{package.json,*.yml}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false
참고
미해
코드 및 파일 컨벤션
- 저는 CheQuiz가 다라서 최종 팀 프로젝트를 해보신 팀원분들의 좋은 의견을 듣고 싶습니다..!
- 개인적으로는 src/pages의 폴더명을 ~Pages 라고 통일했듯 src/api폴더의 파일들도 이름 형식을 통일하면 좋을 것 같아요!
- foundation (이전의 theme 대체) 폴더의 위치를 어디로 하면 좋을까요? 이전 코드리뷰로 남겨주신 것처럼 constants 내부에 넣는 것이 좋을까요?
- 참고 링크 constants/styles.ts/전부 넣기
창민
프로젝트 세팅
- 필요한 것들만 세팅을 다시 하고 싶다
폴더 구조
- 폴더가 너무 많아도 그리고 너무 중첩되어도 좋지 않다
- 타입이 중복되어 사용된다면
@types
폴더 하나에서 관리할 수 있도록 하자
.d.ts
파일보다는.ts
파일에서 export 를 이용하자
├── api ├── assets ├── components │ └── Button │ ├── Button.styles.ts │ ├── Button.tsx │ ├── Button.helper.ts │ └── index.ts ├── constants ├── hooks ├── context ├── pages ├── routes ├── styles ├── types └── utils
스타일드 컴포넌트
- 오브젝트 형태로 통일하자
- 현재
common.ts
에 있는 컴포넌트를components
폴더로 옮기자 ⇒ 이것도 또한 컴포넌트이기 때문에
CSS 프로퍼티 순서
.selector { /* Positioning */ position: absolute; z-index: 10; top: 0; right: 0; /* Display & Box Model */ display: inline-block; overflow: hidden; box-sizing: border-box; width: 100px; height: 100px; padding: 10px; border: 10px solid #333; margin: 10px; /* Color */ background: #000; color: #fff /* Text */ font-family: sans-serif; font-size: 16px; line-height: 1.4; text-align: right; /* Other */ cursor: pointer; }
import 문 정렬
"import/order": [ "error", { "groups": ["builtin", "external", "internal", ["parent", "sibling"], "index"], "alphabetize": { "order": "asc", "caseInsensitive": true }, "newlines-between": "always" } ]
네이밍
컴포넌트명, 타입은 파스칼 케이스 ex) Header, Button
변수명, 함수명은 카멜 케이스 ex) isLogin
Props interface
Props
는 해당 타입이 사용되는 파일 내부에 정의한다interface Props { name: String, password: String } const Login = ({ name, password }: Props) => { return (); }
이벤트 핸들러 함수
Prefix로
handle
을 사용한다const handleChange = () => {}
핸들러 함수의 타입은
React.ChangeEventHandler<HTMLInputElement>
와 같은 형태로 통일한다onChange: React.ChangeEventHandler<HTMLInputElement>
Prettier
코드 포맷팅을 위한 Prettier 설정은 아래와 같다
{ "printWidth": 120, "tabWidth": 2, "singleQuote": true, "semi": true, "bracketSpacing": true, "bracketSameLine": false, "jsxSingleQuote": true, "trailingComma": "all", "useTabs": false, "singleAttributePerLine": true, "endOfLine": "auto" }
함수 정의
function 키워드 대신 화살표 함수를 사용한다
const handleSubmit = () => {}; const ComponentName = () => { return <div>hello</div>; }; export default ComponentName;
Container? Wrapper?
단일 요소를 감싸야 한다면
Wrapper
<Wrapper> <span>hi</span> </Wrapper>
2개 이상의 요소를 감싸야 한다면
Container
<Container> <img src='/img/good.png' /> <span>hi</span> </Container>
코드를 보고 느껴지는 문제점
준혁
현재 담당 부분 코드의 문제점 살펴보기
QuizSolvePage와 QuizResultPage 두 페이지를 담당했지만, 두 페이지 간 문제점을 대부분 공유하고 있으므로 하나의 페이지를 기준으로 문제점을 살펴보도록 한다.
QuizSolvePage
[ 코드 ]
현재 QuizSolvePage의 역할은 다음과 같다.
- 서버로부터 퀴즈 데이터를 불러온다.
- 이때, 메인 페이지에서 랜덤을 선택했는지, 퀴즈 세트를 선택했는지에 따라 불러오는 데이터의 종류가 다르다.
- 사용자가 선택한 답 처리
- 사용자 점수 처리
- 사용자가 선택한 답을 서버에 제출
react-slick
settings 처리
- 컴포넌트 렌더링
QuizSolvePage만 약 200줄 되는 코드가 존재한다.
QuizSolvePage 하나가 담당하는 역할이 너무나도 많음을 알 수 있다.
[ 해결 방안 ]
각 데이터와 데이터를 다루는 메서드들을 공통된 관심사별로 묶어, custom hook으로 관리한다.
이때, 각 hook이 단일 책임 원칙을 위반하지 않는지 주의한다.
다음은 뭉쳐져있는 로직들을 어떻게 분리할 것인지 대략적인 예시를 적어보았다.
- 각 데이터와 데이터를 다루는 메소드를 각각
useQuiz
,useUserAnswer
,useUserScore
등으로 나눈다.
- 정의된 메서드들을 바탕으로 좀 더 도메인에 의존적인
useQuizSolve
,useQuizResult
hook을 구성하여 각 페이지에서 사용할 수 있도록 한다.
storage에 접근하는 부분은 side effects이기 때문에, hook 내부 메서드의 매개변수로 분리할 수 있으면 좋을 듯 하다.
이벤트 함수들을 분리한다.
해당 함수들은 hooks가 아닌 컴포넌트에서 처리한다.
개인이 생각하는 리팩터링 방향
준혁
리팩토링의 목적
- 소프트웨어를 Soft하게
- 새로운 기능 추가를 위한 초석
- 유지 보수성 증가
리팩토링 원칙
우선 담당했던 코드의 문제점을 살펴보기에 앞서, 어떤 방향으로 리팩토링을 진행하면 좋을 지에 대해 고민을 해 보았다.
- 클린 코드 아키텍쳐를 따라 5개의 계층으로 나눠져있는 계층형 구조를 지향하고자 하였으나, 해당 부분을 바로 적용하기에는 도메인 수도 적을 뿐더러, 현재 클라이언트에서 실질적으로 다루는 비즈니스 로직이 없다.
- 해당 부분에 대한 학습을 덜 해서, 당장 적용하기에는 무리가 있다.
- 따라서 SOLID 원칙을 기반하여 컴포넌트를 리팩토링한다. (SOLID 원칙 중 리스코프 치환 원칙은 적용하지 않는다.)
디자인 패턴
hooks 패턴을 활용하여 리액트의 개발 방향성과 맞도록, 다음과 같이 프로젝트를 리팩터링한다.
- custom hooks 사용으로 같은 관심사를 묶는다. custom hook은 상태와 상태를 다루는 로직을 다룬다.
- 데이터와, 해당 데이터를 다루는 메서드들을 묶는다.
- 이때, hooks 밖으로 뺄 수 있는 유틸 함수들을 분리할 수 있다면 분리한다.
- 개발자가 제어할 수 없는 영역인 UI 영역은 hook 내부에 위치하지 않는다.
- 컴포넌트가 직접적으로 다루는 영역은 UI 영역이다.
- 컴포넌트의 구현 부분은 해당 컴포넌트에서 상호작용할 이벤트 메서드를 구현한다.
최종 목표; Headless 적으로 컴포넌트를 구현하자.
Headless란, 사용자에게 보여지는 부분을 과감하게 제거하여 컴포넌트를 구성하는 방법이다.

이런 방식을 통해, 최대한 Headless 적으로 컴포넌트를 구현하는 것을 목표로 한다.
해당 관점에 맞춰서 기존에 작성했던 코드와 구조에 어떤 문제점이 있는지 살펴보자.
미해
리팩토링 방향
누가 봐도 바로 개발을 이어갈 수 있도록 한눈에 이해할 수 있는 프로젝트 코드
- 의미있는 변수, 함수명으로 개선, 복잡한 로직은 중간에 의미있는 이름으로 보기 편하게(개인취향)
- any가 사용된 부분 있다면 올바른 type으로 변경
- 불필요한 인터페이스, 스타일 컴포넌트, 중복된 로직 최소화
제 코드에서 리팩토링할 부분들(안보셔도 아주 무방합니다…)
UserInfoPage에서
닉네임, 비번 추가 모달을 급하게 만들어서 깔끔하지 않음
→ 닉네임 변경 모달과 패스워드 변경 모달을 창민님이 구현하신 방식으로 변경예정
- components/Modal/index.js 참고 예정
UserInfoCard에서
- level이나 currentExp 등 값을 단순히 계산하는 함수는 별도의 util 파일로 분리
- getBadges 함수 별도 파일로 분리, 웹서비스 어디서든 바로 불러 사용할 수 있도록 개선
- UserInfoCard의 데이터 요청 부분은 API 교체 후 로직이 달라질 것으로 예상되어 일단은 그대로 둘 예정
UserInfoTab에서
- 만든/댓글단/좋아요한 퀴즈 각각 state 따로 두지 말고 하나로 합치기
전반적으로
- 의미있는 변수, 함수명으로 개선, any가 사용된 부분 있다면 올바른 type으로 변경
- 불필요하게 추가 선언한 interface가 있다면 기존 interface로 변경
궁금한 점
- UI 개선은 리팩토링 기간 말고 개선 기간 때 진행해야 하는지? 아니면 같이 진행해도 되는지?
- text가 길어지면 overflow 이상해지는 문제 ( 내 정보보기에서 댓글 확인하는 경우)
- 홈페이지 전반적인 컴포넌트의 크기, 디자인 일부 개선 ( 크기 줄이고 균일화)
- 약간의 hover 효과, 애니메이션 등
창민
방향성
코드 리팩터링과 리스트럭처링을 구분하자
지금은 리스트럭처링이 먼저 필요하다고 생각한다
⇒ ex. 컨벤션 적용, 컴포넌트 구조 설계 등
책에서는 TDD 기반의 리팩터링을 소개하고 있고 가장 대표적인 방식이기도 하다
이에 대해 생각만..해보는 것도 좋을 것 같다
컴포넌트 구조
관심사 분리를 최대한으로 해보자
분리 방법은
Hook
을 최대한 활용하자컴포넌트로 분리도 가능하다 ⇒ 너무 무의미한 분리는 오히려 좋지 않다
원칙
리팩터링도 기능 구현과 마찬가지로 PR 단위를 작게 가져가야 한다
리팩터링 2판
책의 핵심인 기능과 UI에는 전혀 변화가 없어야 한다참고
컨벤션
폴더 구조
아래의 폴더 구조를 기본적으로 따른다
├── api ├── assets ├── components │ ├── shared | | └── Button | │ ├── Button.tsx │ | └── index.ts │ └── QuizItem │ ├── QuizItem.styles.ts │ ├── QuizItem.tsx │ ├── QuizItem.helper.ts │ └── index.ts ├── constants │ └── styles.ts ├── hooks ├── context ├── pages ├── routes ├── styles └── utils
타입 지정
표기법은
PascalCase
를 사용한다기본적으로
interface
를 사용한다유틸리티 타입을 적극적으로 사용한다 ⇒
Pick<T, P>
Partial<T>
Props
는 해당 타입이 사용되는 파일 내부에 정의한다interface Props { name: String, password: String } const Login = ({ name, password }: Props) => { return (); }
- d.ts 파일은 사용하지 않는다.
CSS 프로퍼티 순서
기본적인 대분류는 아래 예시를 따른다
.selector { /* Positioning */ position: absolute; z-index: 10; top: 0; right: 0; /* Display & Box Model */ display: inline-block; overflow: hidden; box-sizing: border-box; width: 100px; height: 100px; padding: 10px; border: 10px solid #333; margin: 10px; /* Color */ background: #000; color: #fff /* Text */ font-family: sans-serif; font-size: 16px; line-height: 1.4; text-align: right; /* Other */ cursor: pointer; }
대분류를 제외한 순서 원칙은 아래를 따른다
1. display 2. list-style 3. position 4. float 5. clear 6. width / height 7. padding / margin 8. border / background 9. color / font 10. text-decoration 11. text-align / vertical-align 12. white-space 13. other text 14. content
스타일드 컴포넌트
오브젝트 형태로 통일하자
Import 순서
"import/order": [ "error", { "groups": ["builtin", "external", "internal", ["parent", "sibling"], "index"], "alphabetize": { "order": "asc", "caseInsensitive": true }, "newlines-between": "always" } ]
이벤트 핸들러 함수
Prefix로
handle
을 사용한다const handleChange = () => {}
핸들러 함수의 타입은
React.ChangeEventHandler<HTMLInputElement>
와 같은 형태로 통일한다onChange: React.ChangeEventHandler<HTMLInputElement>
함수 정의
function 키워드 대신 화살표 함수를 사용한다
const handleSubmit = () => {}; const ComponentName = () => { return <div>hello</div>; }; export default ComponentName;
Container? Wrapper?
단일 요소를 감싸야 한다면
Wrapper
<Wrapper> <span>hi</span> </Wrapper>
2개 이상의 요소를 감싸야 한다면
Container
<Container> <img src='/img/good.png' /> <span>hi</span> </Container>
리팩토링 일정
리스트럭처링 후 리팩토링 진행 ~09.23
- 프로젝트 세팅(린트, 패키지 삭제..) -
준혁
(목 저녁까지)
- 폴더 구조 맞추는 작업을 각자
- 컨벤션 지키면서 리팩터링
PR Rule
라인 수만큼 파일 수 변화에도 신경쓰자!
PR 단위
상황에 맞게 본인이 생각하는 적절한 PR 단위를 올리자
하지만 어느정도의 기준은 필요하다
컴포넌트 1개의 변화 = 1개의 PR
리뷰 기간
PR 올라온 뒤 다음 날 23:59 까지
Pn 룰
P1
: Request Changes무조건 반영해야 한다고 생각하는 부분
- 리뷰이는 반영 후 PR 수정
P2
: Comment반영해야 한다고 생각하는 부분
- @멘션을 기반으로 토론 후 반영여부 결정하자
P3
: Approve넘어가도 좋은 의견들이 있다면 남기자
- 이모지를 통해 읽었다는 표시를 남기자
뱅크샐러드 Pn 룰
P1
: 꼭 반영해주세요 (Request changes)
리뷰어는 PR의 내용이 서비스에 중대한 오류를 발생할 수 있는 가능성을 잠재하고 있는 등 중대한 코드 수정이 반드시 필요하다고 판단되는 경우, P1 태그를 통해 리뷰 요청자에게 수정을 요청합니다. 리뷰 요청자는 p1 태그에 대해 리뷰어의 요청을 반영하거나, 반영할 수 없는 합리적인 의견을 통해 리뷰어를 설득할 수 있어야 합니다.P2
: 적극적으로 고려해주세요 (Request changes)
작성자는 P2에 대해 수용하거나 만약 수용할 수 없는 상황이라면 적합한 의견을 들어 토론할 것을 권장합니다.P3
: 웬만하면 반영해 주세요 (Comment)
작성자는 P3에 대해 수용하거나 만약 수용할 수 없는 상황이라면 반영할 수 없는 이유를 들어 설명하거나 다음에 반영할 계획을 명시적으로(JIRA 티켓 등으로) 표현할 것을 권장합니다. Request changes 가 아닌 Comment 와 함께 사용됩니다.P4
: 반영해도 좋고 넘어가도 좋습니다 (Approve)
작성자는 P4에 대해서는 아무런 의견을 달지 않고 무시해도 괜찮습니다. 해당 의견을 반영하는 게 좋을지 고민해 보는 정도면 충분합니다.P5
: 그냥 사소한 의견입니다 (Approve)
작성자는 P5에 대해 아무런 의견을 달지 않고 무시해도 괜찮습니다.Comment Resolve 기준
리뷰어만 할 수 있다
- 리뷰어가 반영 확인 또는 리뷰이의 의견에 대해 납득되어 해결되었다면 resolve를 한다
P3
에 대해서는 코멘트 없이 이모지로 읽었다는 표현을 할 수 있다