Wecode - Project 2 (부트캠프)/세션

Project 2 - 5일차: Peer Review, Refactoring Checklist, Github PR Review 하는 법

JBS 12 2023. 9. 22. 12:20

A. Peer Review

팀원들끼리 서로 코드 리뷰를 하는 것

코드 리뷰를 통해서 얻을 수 있는 것은 매우 많다.

 

더 나은 퀄리티의 코드로 거듭나는 것 뿐 아니라,

새로운 기술적인 지식을 얻을 수도 있고

내가 해결해야 하는 문제에 대해 새로운 관점을 습득 할 수도 

 

대부분의 기업에서는 필수적으로 코드 리뷰 문화가 존재

 

 

🚀 학습 목표

  1. 코드 리뷰의 필요성과 목적 이해하고, 이를 기반으로 리뷰를 진행
  2. 제공 받은 Refactoring Checklist 에 맞춰 자신의 코드를 수정
  3. Github에 PR 을 생성할 수 있고, File Changed 탭을 확인하여 팀원의 코드를 리뷰 할 수 있다.
  4. Github PR의 label/ comment 적극 활용하여, 리뷰어와 소통할 수 있다.
나의 경우에는, 1차 프로젝트에 이미
멘토님과 pr리뷰를 통해 label/ comment 활용 및 pr 생성과 리뷰어와 소통을 경험하였다.
 

Project1- 2일 차: 초기세팅 PR 후 commit (1)

팀 레포지토리에 초기세팅 branch 생성 후, push , pr 요청 후 pr리뷰를 받았다. 1. dependencies 설치npm install express 하고나서는, 계속 npm install mysql mysql2 typeorm cors bcrypt dotenv --save-dev 똑같이 해도 된다 설

pm-developer-justdoit.tistory.com

 

📝 Mentor's Tip

  • 좋은 코드란 복잡하고 어려운 코드, 뛰어난 알고리즘을 구현한 코드 가 아닙니다.
  • 좋은 코드란, 기본적인 것들이 잘 지켜져 있어 누가 읽어도 어렵지 않게 읽을 수 있는 코드
  • 리팩토링 체크리스트에서 기본적인 컨벤션들을 소개하고 있습니다. 이것들을 잘 숙지 하면서 코드를 작성하는 습관을 들여 주세요 👐
  • 개선점을 언급하는 것만이 코드 리뷰는 아닙니다. 팀원이 코드를 잘 작성한 부분이 있다면, 혹은 팀원의 코드에서 내가 아이디어를 습득하여 도움을 받았던 부분이 있다면, 이에 대해서 칭찬을 해주세요! 또, 잘 이해가 가지 않는 부분 / 궁금한 부분에 대해서 질문을 하셔서 의견을 나누는 것도 좋습니다! 적극적으로 의견을 나누는 것을 통해, 자신의 지식을 나누고 공유하는 개발자 문화에 익숙해 지는 것에 포커스를 맞추고 진행해주세요!
  • 피어 리뷰는 양방향 프로세스 입니다. 꼭! 받은 리뷰에 대해서 코멘트를 달거나, resolved 버튼을 누르는 것, 혹은 라벨 을 수정해주는 것으로, 리뷰어에게 해당 리뷰를 확인했고 나의 코드에 반영했다는 것을 알려주세요!

B. 📎 참고 자료 [Backend] Refactoring Checklist

 

😉 개발은 혼자하지 않기 때문에

좋은 코드는 누가 봐도 이해하기 쉬운, 가독성이 좋은 코드

 

혼자서 개발을 하더라도

일주일 전의 나의 코드는 다른 사람이 작성한 코드 같습니다.

 

처음부터 깔끔한 코드를 치기는 어렵지만

여러 번의 수정을 거치면 충분히 깔끔해집니다. 어떤 부분들을 수정할 수 있는지 한 번 볼까요?

 

LEVEL 1

1) Model

1. 모델 이름은 PascalCase, 테이블 이름은 snake_case

 💡 Javascript에서 Class 이름을 생성하는 컨벤션을 따릅니다. MySQL은 snake_case 헷갈리시죠!

  • Model의 이름은 대부분의 언어에서 PascalCase를 사용합니다. 클래스나 타입과 비슷하게 고려합니다.
  • 데이터베이스의 테이블 이름과 모든 컬럼snake_case 를 사용합니다.

 

2. Url은 보통 2000자 이상

💡 데이터베이스 varchar 타입 컬럼에 포함될 수 있는 문자열 길이 중요!

  • MySQL에서 varchar 길이의 기본값이 얼마일까요?
  • 지정된 길이보다 더 긴 문자열이 데이터베이스에 저장된다면 어떻게 될까요?
  • url은 보통 매우 길기 때문에 2000 자 정도 넉넉히 지정합니다.

 

3. created_at과 updated_at

💡 정규화 데이터를 제외하고 created_at 필수로 추가!

  • 사전에 미리 넣어두는 데이터= '정규화된 데이터'  예를 들어 카테고리의 이름등이 있습니다. 한 번 정해지면 별로 변경될 일이 없는 데이터입니다.
  • 정규화된 데이터 외에 추가되고 수정되는 과정이 자주 일어나는 데이터가 있습니다. 회원가입 하는 사용자, 새로 추가되기도 하고 삭제되기도 하는 상품
  • 자주 수정되고, 이용자가 추가할 수 있는 users나 products와 같은 데이터 created_at 과 updated_at 을 필수로 추가하여 데이터의 생성 및 삭제를 관리해주는 것이 좋습니다.
  • created_at 의 경우 default 로 현재 시간이 입력되도록 해주세요. → 개발자가 입력시간을 지정하지 않아도 데이터가 생성됨과 동시에 현재 시각이 테이블에 기입됩니다.

 

4. id에 auto_increment 옵션 추가

💡 auto_increment는 이전 데이터를 읽어 자동으로 만들어집니다.

  • AI 라고도 표현하는 auto_incrementMySQL에서 기본적으로 제공해주는 옵션입니다. 앞 데이터의 값을 읽어 자동으로 1씩 증가시켜줍니다.
  • id는 데이터가 추가될 때마다 자동으로 하나씩 늘어나야 하고, 개발자가 일일이 지정해주지 않아도 생성되어야 합니다. 따라서 auto_increment 옵션을 주도록합니다.
  • 공식문서를 조금 더 참고해보세요 🙂
  • MySQL :: MySQL 8.0 Reference Manual :: 3.6.9 Using AUTO_INCREMENT
 

MySQL :: MySQL 8.0 Reference Manual :: 3.6.9 Using AUTO_INCREMENT

3.6.9 Using AUTO_INCREMENT The AUTO_INCREMENT attribute can be used to generate a unique identity for new rows: CREATE TABLE animals ( id MEDIUMINT NOT NULL AUTO_INCREMENT, name CHAR(30) NOT NULL, PRIMARY KEY (id) ); INSERT INTO animals (name) VALUES ('do

dev.mysql.com

 

5. Integer(정수)로 관리하는 숫자와 Decimal(소수)로 관리하는 숫자

 💡 가격, 점수 등 소수점 아래 수를 관리해주어야 하는 데이터 유념!

  • 대한민국 원화를 제외한 대부분은 통화(currency)가 소수점 아래 숫자를 이용합니다. 원화도 연산을 하거나 상세한 가격을 관리하면 소숫점 아래가 필요합니다. 따라서 가격은  Decimal 보통 소숫점 아래 4자리 정도까지는 관리. 정수로 관리 하지 않도록 주의
  • 상품에 대한 별점이나, 리뷰에 포함된 스코어 등은 보통 정수로 관리하는 곳이 많지만 0.5 단위로 관리하는 곳도 있습니다. 서비스 기획에 따라 달라지지만, 기획이 변경되어도 유연할 수 있도록 실수(floating-point number; 줄여서 'float') 로 관리해주면 좋습니다.

 

6. one-to-one 관계 테이블 설정

 💡 테이블이 많아지면 그만큼 비용이 발생합니다.

  • 한 테이블에 컬럼으로 들어갈 수 있는 데이터를 one-to-one 관계의 테이블로 분리하여 관리하면 추가적인 리소스가 소요됩니다.
    • SELECT 문으로 데이터를 추출할 때 JOIN 을 이용해야합니다.
    • 데이터를 입력할 때 한 번에 테이블에 두 개에 데이터를 넣어야 하므로 INSERT INTO 문을 2회 사용해야합니다.
  • 따라서 데이터의 구분을 위해서 테이블을 분리하는 것이 효율적일지, 쿼리문의 효율성을 위해서 한 테이블에서 관리하는 것이 좋을지 많은 고민을 거쳐야합니다.

 

2) Server

1. 환경 변수 숨기기

💡 Database 정보, PORT 번호, secretKey 등은 .env 파일에 숨겨두기!

  • server.js 파일에서 사용하는 고유한 상수 변수들은 중요한 정보일 때가 많습니다.
  • 노출될 경우 보안상 위험한 정보들은 .env 파일에 숨겨두고, 필요한 정보만 import 해서 사용합니다.
  • 물론 .env 파일은 보안이 지켜져야 하기 때문에, github에 올라오면 안됩니다. .gitignore 에 꼭 포함되어 있어야 합니다.

참고자료

 

Project 1 - 1일 차: 초기환경 세팅 [체크리스트]

[[ 프로젝트 초기 세팅 체크리스트를 하면서, 배운 것들]] 1. root 폴더 | 초기세팅시 진행사항 pull_request_template.md 상세하게 작성 .gitignore 에 다음의 자동생성 사이트를 이용하여 vim, macOS, node, linux,

pm-developer-justdoit.tistory.com

 

2. 에러 핸들링을 위한 try-catch디버깅을 위한 console.log() 사용

💡 서버 구동 중 에러가 발생 했을 때에는, 에러를 그대로 return만 하기보다는 console.log()로 백엔드 터미널에서도 확인하시는 것이 좋습니다.

  • try-catch 문법을 학습하신 후, 에러가 발생 했을 때 catch 를 왜 해주어야 하는지 생각해보세요.
 

Project2- try catch 문법 복습 **

try catch에서 에러가 발생 했을 때 catch 를 왜 해주어야 하는지 ChatGPT try와 catch는 에러 핸들링(오류 처리)을 위한 구문입니다. 프로그래밍 코드를 실행하는 동안 예외(에러)가 발생할 수 있으며, 이

pm-developer-justdoit.tistory.com

  • 또한 에러는 서버 터미널에서 확인하는 습관을 길러야 하므로, catch 문 내에서 console.log() 로 에러를 시각적으로 표시해주세요.
  • console.log() 가 있을 때와 없을 때 각 상황에서 에러를 발생 시켜 차이점을 확인해주세요.
try {
  server.listen(PORT, () => console.log(`Server is listening on ${PORT}.`));
} catch (err) {
  console.log(err); // HERE!
} finally {
}

 

3. JSON 데이터의 key에 변수 사용 ❌

 💡 객체의 키에는 변수를 담지 않습니다.

  • 데이터를 나열할 때, 키에 순서를 담고 싶을 수 있습니다.
  • 그러나 객체는 index 로 표현되는 일종의 순서가 없기 때문에, 순서를 정할 수 없습니다.
  • key-value 의 사이에서 key고정된 이름으로 사용합니다.
// BAD 🙅
{
  "data" : [{
    "1" : "콜드 브루 커피"
  }, {
    "2" : "브루드 커피"
  }, {
    "3" : "에스프레소"
  }, {
    "4" : "블렌디드"
  }]
}
// GOOD 🙆
{
  "data" : [{
    "id" : 1,
    "name" : "콜드 브루 커피"
  }, {
    "id" : 2,
    "name" : "브루드 커피"
  }, {
    "id" 3,
    "name" : "에스프레소"
  }, {
    "id" : 4,
    "name" : "블렌디드"
  }]
}

 

4. JSON 데이터 naming convention 통일

💡 snake_case, camelCase 모두 무관합니다. 중요한 것은 통일시키는 것!

 

참고

 

Project 2 - json 복습

JSON "JavaScript Object Notation" 데이터를 교환하고 저장하기 위한 경량의 데이터 형식 텍스트 기반의 데이터 형식 사람과 기계가 모두 읽고 쓰기 쉽도록 설계 웹 애플리케이션과 서버 간의 데이터 교

pm-developer-justdoit.tistory.com

 

1. get, post 메소드 분리

 💡 client (프론트엔드) 서버 입장에서 get, post를 명확히 생각합니다.

  • get 메소드를 이용할 때 request body 에 데이터를 담지 않습니다. (프론트엔드가 요청, 받을 때 get/ 프론트엔드가 보낼 때 post)
  • request body에 데이터가 있고, 해당 데이터를 이용해 백엔드 데이터베이스에 데이터를 입력하거나 수정해야할 때에는 post 메소드를 이용합니다. (프론트엔드가 백엔드에게 request body에 데이터를 post 한다/ 백엔드는 프론트엔드에게 받는다 --반대) 

LEVEL 2

1) Controller Layer

1. controller의 역할 분리

 💡 request, response 두가지 객체에 대한 과정만 처리

  • 컨트롤러는 외부에서 들어오는 request를 처리하여, response를 만드는 역할만 해야합니다.
  • request header에서 사용자 정보를 가져오거나, request body에서 데이터를 꺼내오는 역할
  • response 객체에 status code를 부여하고, client측으로 전송할 message 및 데이터로 response body를 구성하는 역할

2. 명확한 status code 사용

💡 200, 201, 401, 403, 500 사용은 정확히!

  • 데이터가 생성 되었을 때는 201 status 코드를 사용하도록 해주세요.
  • 사용자를 확인할 수 없을 때에는 401, 사용자가 권한 밖의 request를 보낼 때에는 403을 보냅니다.
  • 서버 에러인 (500)가급적 표출되어서는 안 되며, 백엔드에서 에러 상황을 미리 예외 처리 해두어야 합니다.
  • 현업에서는 회사 내부의 custom status code를 만들어 에러 상황을 보다 세분화하여 이용하기도 합니다. 서비스를 이용하다 만나게 되는 DS40592 와 같은 문자열이 커스템 에러 코드입니다.

3. message convention 통일

res.status(201).json({
  message: "CREATED",
})

res.status(403).json({
  mesaage: "PERMISSION_DENIED",
})
  • 응답 객체에 포함된 메세지의 형식을 통일해주어야 합니다.
  • 대문자 + 띄어쓰기(_) 조합을 많이 활용합니다. 이는 회사마다 서로 다른 컨벤션을 이용하기도 합니다.

4. Request body 객체 구조분해할당

const name = req.body.name
const email = req.body.email
const password = req.body.password

보다는 

**const { name, email, password } = req.body**
  • 구조분해 할당을 이용해 깔끔한 코드를 작성합니다.

 

5. KeyError 반드시 예외처리

 💡 빈 값이 들어올 때가 아니라, client에서 보낸 객체에 해당 key가 없을 때 발생하는 에러!

  • 회원가입할 때에 필수 키는 어떤 것들이 있을까요? email, password 라고 가정. client에서 보낸 body 객체는 아래와 같이 구성되어 있어야 합니다.
  • body = { email : "CodeKim@wecode.co.kr", password : "helloWecodeI'mCodeKim" }
  • 필수 키인 email 또는 password 가 입력 되어 있지 않다면 KeyError 가 발생합니다. 아래와 같은 경우 처럼 필수 키 가운데 하나라도 누락된다면 발생합니다.
    body = {
      password : "helloWecodeI'mCodeKim"
    }
    
    body = {
    }
    
  • body = { email : "CodeKim@wecode.co.kr" }
  • 다음과 같은 경우는 KeyError 가 아닙니다. body 객체 내부에 필수 key가 모두 있으므로 KeyError가 아닙니다. 이런 경우는 email과 password가 형식에 맞지 않은 상태입니다. 비즈니스로직에 포함되므로, service layer에서 예외처리합니다.
  • body = { email : "", password : "" }

 

2) Service Layer

1. service의 역할 분리

💡 비즈니스 로직이라고도 불리는 service layer에서는 request 검증 이후, 데이터베이스에 접근하기 전까지 모든 비즈니스에 대한 함수가 만들어집니다.

데이터베이스에서 꺼내온 데이터 객체를 가공하는 함수 또한 만들어집니다.

  • 더이상 req 나 res 를 매개변수로 하는 함수를 생성하면 안됩니다. (controller의 역할이니까요🙂)

 

2. 단순 매개 역할

 💡 특별한 기능을 하지 않을 수 있음.

  • 단순히 Dao layer에 있는 함수만 호출하여 return 하는 경우도 간혹 있습니다.
const findDrinks = async () => {
  return await productDao.findDrinks();
}

 

3. 데이터 없을 경우 예외 처리

💡 빈 배열 반환 vs 404 Not found 에러

  • 위 2번 예시에서 productDao.findDrinks() 의 반환 값이 빈 배열이라면 어떨까요? 데이터가 하나도 없는 경우입니다.
  • 빈 배열을 그대로 반환하기도 하며, 기획에 따라 데이터가 없는 경우에 대한 예외처리를 하기도 합니다.
  • 이러한 경우에도 404 에러 코드를 사용할 수 있습니다. Resource를 찾을 수 없기 때문입니다.
  • 에러 객체를 다음과 같이 throw 할 수 있습니다. throw된 에러 객체findDrinks 함수를 호출한 곳으로 반한됩니다. controller로 가겠죠?
const findDrinks = async () => {
  const drinks = await productDao.findDrinks();

  if (!drinks.length) {
    const err = new Error("DRINKS_NOT_FOUND");
    err.statusCode = 404;
    throw err;
  }

  return drinks

 

3) Model Layer

1. model의 역할 분리

 💡 데이터베이스 모델 만드는 schema를 부르는 명칭이 아님. server를 구성하는 파일 가운데 데이터베이스를 관리하는 코드가 모여있는 파일을 칭함.

  • 데이터베이스를 대상으로 데이터를 가져오거나, 추가하거나, 수정하거나, 삭제하는 명령을 내리는 코드가 모여있는 모듈입니다.
  • 그 외 다른 로직을 처리해서는 안됩니다. (if문이나 for문등이 등장하는 경우는 굉장히 드뭅니다.)

 

2. 코드 가독성을 위한 SQL 컨벤션

💡 SQL 문법이 시작되는 곳에서 줄 분리!

  • 일반적으로 테이블의 컬럼이 많고, 변수도 많기 때문에, SQL문을 가독성 높게 작성하기 어려울 수 있습니다.
  • const createUser = async (name, email, password) => { return await prisma.$queryRaw(` INSERT INTO users (name, email, password) VALUES ('${name}', '${email}', '${password}') `) }
  • SQL 문법들을 기준으로 줄을 분리해주면 깔끔한 코드가 됩니다. 이는 위코드 컨벤션으로, 회사마다 팀마다 컨벤션은 다를 수 있습니다.
  • const createUser = async (name, email, password) => { return await prisma.$queryRaw(` INSERT INTO users ( name, email, password ) VALUES ( '${name}', '${email}', '${password}', ) `) }

 

3. 데이터 입력 혹은 선택시 하드 코딩 no! 

💡 유연한 코드를 위하여 SQL 문 내부에 하드 코딩 대신 변수를 사용!

// HARD CODING 🙅
const createUser = async (name, email, password) => {
  return await prisma.$queryRaw(`
    INSERT INTO users (
      name,
      email,
      password
    ) VALUES (
      'CodeKim',
      'codeKim@wecode.co.kr',
      'jefieut@393404384n!',
    )
  `)
}
// USING VARIABLES 🙆
const createUser = async (name, email, password) => {
  return await prisma.$queryRaw(`
    INSERT INTO users (
      name,
      email,
      password
    ) VALUES (
      '${name}',
      '${email}',
      '${password}',
    )
  `)
}

혹은  { id:5 } ->변수화

 

4. [Advanced] SQL Injection 방지

💡 쿼리문이 단순 텍스트인 점을 이용하여 기존 명령문을 지우고 다른 명령어 (ex. DROP DATABASE;)로 대체해버리는 해킹 방법

  • 대부분의 ORM이 SQL Injection을 방지하기 위한 방법을 도입해두었습니다.
  • SQL injection 해킹에 대해 추가 공부를 진행한 후, 해당 해킹을 방지할 수 있도록 함수를 변경해보세요.

C. 📎 참고 자료 [Github] PR Review

 💡 Github PR 을 통해 서로의 코드를 확인하고, 리뷰하는 방법에 대해서 함께 알아보도록 하겠습니다.

우리팀 pr

 

Project 2- 5일차 (2): [회원가입 PR 과정, comment, 깃허브 현업 활용]

할때마다 commit 하라고 해서, git add . git commit -m 기능별로 commit을 하라는건, push까지 해야 한다는 뜻. m pr: 큰 기능에, feature branch의 마감 commit message에 어떤 기능별로 오면, 그거만 보면 되는데, 한

pm-developer-justdoit.tistory.com

 

Project 2 - 5일 차 (9) : 내가 push 한 '로그인 함수' : pull request 팀원 리뷰 + 수정

올리고 몇 분 사이 바로 올라오는, 팀원분의 빠른 피드백 좋다 추석 연휴 전 일단, commit, push 하는 게 좋겠다 싶어서 올려놓고 보느라 수정 사항이 좀 있지만. 과정에서 배우는 게 중요하니까. 나

pm-developer-justdoit.tistory.com

1. Github PR을 통해 코드 확인

2. 리뷰 작성

3. 리뷰 마무리

4. 라벨 관리

 💡 리뷰어와 소통하기 위한 방법 중 한 가지로 라벨이 있습니다. 위코드에서 사용하는 라벨의 종류는 여러가지가 있습니다. 각각의 라벨이 무엇을 의미하는지 어느 경우에 사용하는지 알아 보도록 하겠습니다.

  1. Status: 수정 요청
    • 리뷰어가 리뷰를 완료한 뒤, 리뷰한 사항에 대해 수정을 요청하기 위해 사용하는 라벨입니다.
  2. Status: Accepted
    • 리뷰 받은 사항에 대해서 반영이 완료되어, master 브랜치에 merge 완료된 브랜치를 표시하기 위해 사용하는 라벨 입니다.
  3. Status: Conflict 해결 요청
    • master 브랜치와 코드 충돌이 나는 경우에 사용합니다. 보통 다른 팀원의 작업물이 master 브랜치에 merge가 되었는데, 해당 업데이트 사항을 나의 브랜치에 반영하지 않았을 때 발생합니다.
  4. Status: 도움 요청
    • 혼자서는 해결할 수 없는 문제가 발생했을 때, 도움을 요청하기 위해서 사용합니다.
  5. Status: 리뷰 요청
    • 리뷰어에게 작업한 코드에 대해서 리뷰 요청을 할 때 사용합니다.
  6. Status: 리뷰 진행 중
    • 리뷰어의 리뷰가 진행 중임을 알리기 위해 사용합니다.
  7. Status: 추가 기능 구현중
    • 해당 브랜치에 아직 추가적으로 구현되어야 하는 기능이 남아 있음을 의미합니다. 이 라벨이 달려있을 경우, 리뷰 반영이 완료 되어도 master 브랜치에 merge 하지 않습니다.