사내 코드 리뷰를 통해 알게된 점 및 개선점

2023. 10. 30. 19:59프로그래밍

- 프론트엔드와 백엔드의 유효성 검사는 이중으로 처리해라

프론트 단과 백엔드 단의 validation(유효성 검사)은 모두 해줘야한다.

왜냐하면 프론트에서 정확하게 유효성 검사를 실시해 값을 넘겨줬다 하더라도 백엔드에서 api호출을 통해 원치 않은 데이터를 넘겨줄 수 있기 때문이다. 가령 /api/deleteBoard/:id와 같이 user를 가입시키는 api가 있다고 했을 때 api의 url만 알 수 있다면 어떤 데이터든지 접근해서 삭제할 수 있기 때문이다. 따라서 먼저 세션이 있는지 검사할 것이며 그 세션의 아이디와 그 세션의 아이디와 삭제하려는 게시글이 서로 같은지를 비교하는 유효성 검사가 필요하다. -> 실제로 api에 대한 요청을 Postman을 이용해 요청했을 때 게시글이 삭제되는 문제를 확인했다.

 

- 객체의 프로퍼티에 접근할 때에는 유효성 검사를 철저히 하자. Javascript를 사용한다면 옵셔널 체이닝(?.)을 잘 쓰자

대표적으로 req객체가 있다고 가정해보자. req객체안에는 body가 있고 body안에는 또 여러개의 객체를 가질 수 있다. 이 때 해당 객체의 프로퍼티에 접근하기 위해서는 다음과 같이 코드를 작성할 것이다.

const id = req.body.user.id;

그러나 이런 경우에 user가 null이거나 undefined일수도 있기 때문에 이럴 경우 크래시가 발생한다. 

그렇기 때문에 일반적으로 다음과 같이 두가지 방법으로 접근할 수 있다.

// 1번 방법
const id = req.body.user && req.body.user.id;
// 2번 방법
const id = req?.body?.user?.id;

이렇게 되면 id에는 설령 중간의 프로퍼티가 null이나 undefined이더라도 크래시가 발생하지 않고 그 값(null or undefined)이 담기게 된다. 따라서 거기에 대한 처리만 따로 해주면 된다.

 

- 암호화와 복호화를 이용해 사용자의 민감한 개인정보를 처리해보기

혼자 프로젝트를 진행할때는 기능 구현이 우선시였기 때문에 세부적인 부분은 생각지 못했다. 그러나 실제로 서비스 되는 부분을 개발해야 할 때는 보안이나 데이터의 중요성은 굉장히 중요하다. 따라서 패스워드와 같은 정보는 암호화를 통해 데이터베이스에 저장해주는 편이 좋다. spring을 쓴다면 spring security, node에서는 자체 내장 모듈인 crypto나 외부 모듈인 bcrypto를 사용하면 된다.

암호화와 복호화는 또 단방향과 양방향으로 나뉘어지게 된다.

단방향 방식은 암호화만 되고 복호화는 할 수 없음을 뜻한다. 일반적으로 해시 함수를 통해 데이터를 암호화하고 난 후에 db에 암호화한 결과를 db에 그대로 삽입한다.  그래서 로그인 처리에도 같은 텍스트를 또 다시 암호화 시켜서 db와 비교하는 식으로 구현한다.

양방향 방식은 암호화와 복호화 모두 수행할 수 있다. 

 

- DB를 조회할 때는 조회되는 컬럼을 최소화해라.

이 부분은 기존에도 알고 있던 부분이긴 했지만 확실히 조금 더 신경써야할 필요가 있을 듯 하다. 당연하게도 int형은 일반적으로 필드 자체가 2byte이고(mysql의 경우 몇 바이트인지 모르겠지만) 문자열은 영어 기준 1byte, 한글 기준 2byte이기 때문에 필드를 조회할 때 더 많은 시간이 소요되기 때문이다. 따라서 필요한 데이터가 아니라면 가급적으로 정수형으로 컬럼을 조회하고 쓸데없는 컬럼은 조회하지마라.

 

- HTTP 메서드의 종류를 알고 그에 맞게 router에  request method 타입을 할당하자.

HTTP메서드는 GET, POST, PUT, DELETE, PATCH.. 등등이 있다. 여기서 내가 착각하고 있던 것은 POST메서드로 컨트롤러에 요청했다고 해서 컨트롤러의 요청 메서드를 POST로 설정해놓으면 post요청만 받으므로 안전하다고 생각했다. 그러나 POST는 그냥 외부적으로 데이터를 노출 시키지 않고 데이터를 전송할 뿐 위에서 서술했듯 POST로 데이터를 요청할 수 있는 방법은 얼마든지 많다.

어쨌든 router를 사용할 때 get과 post이외에도 delete나 put과 같은 메서드도 있기 때문에 다음과 같이 명시하면 조금 더 가독성이 좋은 코드를 작성할 수 있다. 

app.post('/deleteBoard'...)
app.post('/updateBoard'...)

app.delete('/board')
app.put('/board')

이렇게 method 타입을 명시하면 다음과 같이 굳이 반복되는 문자를 입력하지 않아도 해당 라우터가 어떤 로직을 처리하는지 한눈에 알아보기 쉽다.

 

 

- 프론트단으로 넘어온 서버의 데이터가 유효성 검사가 됐다하더라도 프론트에서 한 번 더 검사하자.

res.json(data); 을 통해 response객체에 data를 넘겨 보냈다고 가정하자. 이 때 뷰페이지에서는 해당 값에 접근할 수 있게 된다. 물론 요청을 날리는 controller에서도 해당 data변수에 대한 유효성 검사가 실시가 돼야 하겠지만 프론트엔드에서도 해당 객체를 사용할 때 한번 더 확인해주는 것이 필요하다. 예를 들어 게시글 목록을 불러와야 해서 boardList라는 변수를 response객체에 담아줬다고 하자. 

이때 for문을 이용해 viewPage에 게시글을 보여주려고 할 때 이 객체가 진짜 배열인지, 인덱스로 접근해도 되는지 등에 대한 유효성 처리도 꼼꼼히 진행해야 한다. 

 

- 객체나 변수, 함수, 컨트롤러의 url등의 네이밍을 직관적으로, 통일감있게 설정하자. 

이건 경험과 센스와 관련이 있는 말 같은데 예를 들어 사용자의 중복되는 이메일이 있는지 확인하고 그에 대한 결과값을 emailCheck라는 변수에 저장하려고 한다고 가정하자. 이 때 check라는 단어는 boolean 타입일 것 같은 느낌이 든다. 바로 if문에서 사용해도 될 것 같은 느낌을 준다는 뜻이다. 그러나 실제로 내가 사용했을 때는 단순히 이메일을 체크하고 난 후의 결과값을 뜻하는 변수명이었다. 또한 url을 매핑할 때도 마찬가지이다. /board/boardView와 같이 설정했는데 앞쪽에 board가 있으니 굳이 board를 한 번 더 작성할 필요가 없다.

/board/view 와 같이 나타내도 된다는 것이다.

 

 

대체적으로 네이밍 센스에 대한 지적을 좀 많이 받은 것 같다. 이 부분에 있어서는 다른 사람도 읽기 쉬운 코드를 작성하기 위해 가독성 좋은 코드를 작성하려고 계속 노력해야 계선점이 생길 것 같다. 끝.

 

- 특정 url의 접근을 막기 위한 validation을 추가하기 위해서는 validation middlewear를 추가하자. 

로그인하지 않았을 때, 혹은 특정 권한이 없는 사용자에게는 해당 url로의 접근을 막아놓을 필요가 있다. 이를 위해 사용할 수 있는 가장 비효율적인 방법은 라우터별로 모두 세션검사를 해주는 것이다. 그보다 나은 방법은 특정 url의 형태로 들어왔을 때 모두 동일하게 세션검사를 수행하기 위해 와일드카드를 이용할 수도 있다. 이것보다 더 좋은 방법은 와일드카드도 적용하되 api와 같은 컨트롤러를 호출할 때에는 세션검사를 필수적으로 진행해야