[쇼핑몰 프로젝트 6일차] 돈받고 이렇게 짜면 큰일나겠는데 + 리팩토링, 순수 함수에 대한 고민

코드리뷰 수정

중대한 코딩 실수

수정한 함수에서 주석을 수정하지 않아서 주석의 내용이 틀리고(모르는 사람이 보면 큰일남) 안 쓰이는 함수가 들어가있고 심지어 아무도 못본새라 다행이지만 불필요한 함수를 삭제하면서 필요한 함수도 삭제해버리기도 했다.

이게 정말 부끄럽다고 느껴진게, 본인이 짠 코드임에도 불구하고 한 번 슥 봤을 때 필요없는 함수라고 인식해서 삭제했다는 것이다.

다른 사람이 볼 때 얼마나 함수의 쓰임을 예측하기 힘들지 생각하면 소름이 돋는다. 반성하게 됐다.

특히 주석이 틀리는 것은 막상 고칠 땐 코드의 알고리즘을 수정하는 것에 비해 상대적으로 간단한 일이지만, 간단한 만큼 협업할 땐 유의해야 될 부분이다.

해당 함수를 다른 팀원들이 건드리지 않아서 나만 알고 고치는 거지만 잘못된 주석 때문에 코드를 여러 번 읽으면서 팀원들을 괴롭힐 일은 없어야 되기 때문이다.

사실 팀원들이 보고도 별 생각 없이 넘어갔을 수도 있지만, 지금까지 겪어온 팀원들의 성격이 의견 공유를 적극적으로 하고 책임감이 강하기 때문에 만일 봤다면 알려줬을 것이다.

역시 이건 팀원들이 건들지 않아서 모르는 상태가 맞는 것 같다. 다행이다

리팩토링과 순수 함수에 대한 고민

코치님께서 내가 짠 함수는 리팩토링이 필요하다는 리뷰를 남겨주셨고,

리팩토링이라면 코드를 더 알기 쉽게 함수를 뜯으라는건가?

싶어서 다음과 같이 코드를 바꿔버렸다.

async function getUserInfo() {
    try {
	const userData = await Api.get('/api/update');
	fullNameInput.value = userData.fullName;
	if (userData.phoneNumber) {
	    phoneNumberInput.value = userData.phoneNumber;
	}
	if (userData.address) {
	    addressInput.value = Object.values(userData.address).join(' ');
	}
	selectWrite();
    } catch (err) {
	    console.error(err.stack);
	    alert(err.message);
	    location.href = `/login/${['payment', '']}`;
    }
}

이랬던 코드가

function getUserName {
    fullNameInput.value = userData.fullName;
}
function getUserPhoneNumber {
    if (userData.phoneNumber) {
	phoneNumberInput.value = userData.phoneNumber;
    }
}
function getUserAddress() {
    if (userData.address) {
	addressInput.value = Object.values(userData.address).join(' ');
    }
}
async function getUserInfo() {
    try {
	const userData = await Api.get('/api/update');
	getUserName();
	getUserPhoneNumber();
	getUserAddress();
	selectWrite();
	} catch (err) {
	    console.error(err.stack);
	    alert(err.message);
	    location.href = `/login/${['payment', '']}`;
	}
}

이렇게 되었다.

설명을 듣고 문제점을 알고 나서 보니까 정말 웃긴 코드이다. 그냥 북북 찢어버렸을 뿐 근본적인 문제의 해결이 전혀 안되었다!

오전 2시에 머지를 하다가 새벽에 활동하는 것을 들킨 김다운.

코치님께서 곧바로 함수는 최대한 순수 함수가 되게끔 해야 된다고 피드백을 주셨다.

새로 알게 된 키워드: 순수 함수, 함수형 프로그래밍

찾아보니 외부의 상태나 함수 내의 인자 상태를 변경하지 않고 하나의 기능을 하는 함수라고 볼 수 있었다.

그런데 찾아볼수록 ‘모든 함수가 순수 함수인건 불가능할텐데 어딜 어떻게 변경해야 되는거지? 이거 기준이 있나?’ 는 생각이 들어 머리에 버퍼링이 걸렸다.

내 반응이 로딩중인 컴퓨터 같다는 상태인걸 바로 알아차리신 코치님께서 도움의 손길을 내미셨고, 그렇게 오전 3시 반부터 4시 반까지 화기애애한 분위기 속에서 코치님께 일대일 명강의를 들을 수 있었다.

직접 코드를 작성해가면서 사고의 흐름을 보여주셨는데, 그야말로 코드에 실시간으로 마법이 일어나는 것 같았다. 내가 짰던 비루했던 코드가 마치 거울을 보며 “이게…나?” 하고 빛나는 것만 같았다… 미안해 코드야

한 번 보고 체화해서 직접 짤 수 있는 수준의 코드가 아니었지만, 적어도 보고 무슨 코드인지 이해하기가 훨씬 수월하게끔 바뀌었다는 것은 내 부족한 식견으로도 알 수 있었다.

내가 지금까지 하고 있던건 그저 명세사항을 그대로 코드로 옮기면서 “이 정도면 알아보기 편하겠지?” 하는 수준이었던 것이다. 처음부터 끝까지 내가 짜긴 했지만, 너무나도 불친절한 코드였다.

코치님께서 보여주신 마법(?)으로 내 코드에서 문제점이 명확하게 보였다.

문제점 1 | 함수가 이름값을 못하고 있었다.

단순히 정보를 가져오는 함수랍시고 이름에 get을 붙였었는데, 이름과 다르게 return 값이 없어서 추측하기 좀 어려울 수 있다는 피드백을 받았다.

유저 정보를 가져오니까 getUserInfo인데요!? 라고 단세포적으로 반응할 뻔했는데, get이랑 return값을 엮어서 생각해보니 학부 때 C++을 배웠을 때에도 getter는 가져오는 값을 return한다는 특성이 있었다는 것이 생각났다. 사실 얼마 전에 JS 배울 때에도 나왔던 개념이었는데 사용하지 않아서 잠시 잊고 있었다. 잊고 사용하지 않았다는건 결국 모른다는 뜻과도 같았다. 지금까진 그냥 클래스에서 변수 세팅할 때나 쓰는건줄 알았는데, 알고 나니 마땅히 내 코드에 사용이 시급한 중요한 개념이었다. 이 함수에서 getter를 바람직하게 사용한다면 당연히 유저의 이름, 전화번호, 주소를 각각 반환하는 역할을 할 것이다. 그 중 하나를 보면 다음과 같다.

function getUserName(user = {}) {
  return user.fullName;
}

타입스크립트가 떠오르는 모양새이고 비슷한 느낌으로 안정성을 주고 있다.

무조건 객체 상태를 가져오고, 동일한 입력에 동일한 출력만 하고, 외부의 값을 변경하거나 영향을 받지도 않아 순수 함수를 만족한다!

문제점 2 | async/await을 사용하여 비동기 처리를 하고 있음에도 이를 제대로 활용하지 못했다.

단순히 async await을 덜렁 붙인다고 비동기 처리가 완성되는 것이 아니었다.

내가 작성한 유저 정보 관리 페이지는 1. 페이지 로드 시 유저 정보 불러오기 > 2. 유저 정보 수정 후 저장 클릭 > 3. 수정된 유저 정보들을 유효성 검사 > 4. 모든 유효성 검사를 통과했을 때만 객체를 생성하여 DB에 추가 라는 순서를 거친다.

나는 12&3&4를 각각 하나의 함수로 총 두 개의 함수에 이를 작성했다. 첫 번째 함수도 순수함수에 위배되지만, 두 번째는 비동기 처리도 구리게 구현해놨다.

async function getUserInfo() {
  const userData = await getUserData();

...
}

아… 이런 방법이…

코드가 세련되게 변했다.

문제점 3 | try-catch를 꼭 쓸 필요는 없었다.

꼭 쓸 필요는 없었다고 표현하니까 별 것 아닌 것 같지만, 이게 프로그램이 커지고 try-catch문이 계속 늘어나면 비동기 처리를 하는 것이 어려워진다(side effect 발생).

async function getUserInfo() {
  
  ...

  if (isError) {
    
    ...

  }

  ...
}

역시 안 쓸 수 있는 경우라면 머리를 써서 최대한 안쓰는 방향으로 하는게 보기도 좋고 관리하기도 편하다.

느낀점

알고 나니 굳이 복잡하게 코드를 왜 그렇게 썼는지 의문이다. 그런데 방심하면 또 그렇게 작성할 것 같다. 고민을 많이 해야 겠다.

코드를 처음 작성할 땐 물흐르듯 당연하게 생각했는데 지금 보니 당연하지 않았고, 쉬운 길을 두고 복잡하게 돌아가는 기분이 드는 코드들을 잔뜩 만들었다. 그런 면에서 내 코드는 가독성과 유지 보수성이 좋지 못했다.

공부의 필요성

이번 코드리뷰는 한 번에 모든 것을 완벽하게 이해하고 바로 적용할 수 있는 내용이 아니었다.

잊지 않기위해 간단하게 정리한거라 피드백을 100% 기록한 것도 아니고 빠진 부분도 존재한다.

코치님께서 현업을 하며 그동안 개발을 해오면서 끊임없이 고민하며 정립해온 사고의 결과 중 하나를 따라가는 것이었기에 어찌보면 당연했다.

여러 번 복습하면서 스스로 또 코드를 짜보고, 더 알게 되는 부분은 수정하며 추가할 예정이다.

중간중간 잘 와닿지 않을땐 사례를 직접 보기 위해 검색할 만한 키워드를 실시간으로 검색해가며 비슷한 경험들이 있나 찾아보기도 했다.

예를 들어 try-catch문이 늘어나는 경우를 보게 되었는데, 이 블로그의 개발자가 문제를 인식하고 해결하기 위해 노력한 흔적을 따라가며 나도 함께 생각을 하고 문제해결을 위한 사고의 흐름을 볼 수 있어 공부가 되었다.

어떤 문제든 관심을 가지고 찾아보면 어딘가의 누군가가 고민한 흔적을 발견할 수 있고, 같이 생각해볼 수 있기 때문에 단순히 에러 났을 때의 해결책만 찾아보지 말고 다른 사람들이 고민한 사례도 찾아봐야겠다 는 생각이 들었다.

그리고 단순하게 유저 정보를 불러오는 페이지가 필요하네? (몇 시간 후) 짠, 유저 정보를 불러오는 페이지를 만들었습니다. 알아보기 쉬우라고 변수 이름도 친절하게 늘여 썼어요. 이러고 끝낼게 아니라, 함수 단위로 뜯어보면서 내가 짠 함수가 흐름이 매끄러운지, 순수 함수인지 등을 따져보며 항상 코드의 완성도를 높이는 사고를 해야 됨을 깨달았다.

개발자 완전 멋있다.

요즘 핫한 고연봉자들에 대한 얘기가 아니다. 단순히 돈만 많이 벌 생각이었으면 사업을 했을 것이다(창업 생각이 없는건 아니다. 아직 아이디어가 없어서 못할 뿐이다!).

각자의 자리에서 묵묵하게 일을 하는 것을 넘어서, 늦은 시간에도 순전한 호기심과 열정으로 얼굴도 모르는 상대에게 이렇게 적극적으로 지식을 나누고 기뻐할 수 있는 직업은 흔치 않을 것이다.

모든 개발자들이 다 에너제틱한 사람만 있진 않을 것이고 다른 직업들도 나름의 훈훈한 문화가 있겠지만, 개발자들만큼 글로벌한 단위로 온갖 방법을 동원하여 서로 스스럼없이 지식을 공개하고 나누는 공유 문화가 활발한 직업은 없는것 같다.

살아가면서 이왕이면 다른 이들에게 좋은 영향을 주는 사람이 되고 싶기 때문에 이런 부분이 특히 좋다.

그래서 나도 부족하지만 뭐라도 기여해보고 싶어서 오픈소스 프로젝트에 지원했었는데, 워낙 쟁쟁한 사람들도 많고 내가 자기소개서를 잘 못 써서 과연 좋은 소식이 들릴진 모르겠다.

안되더라도 다음에 더 실력 쌓고 도전하면 되고 기회는 많으니 긍정적으로 생각해야겠다. 어쨌든 멘토님들이나 코치님들이 멋지게 설명하는 모습을 보면 이런 생각을 하게 된다.

image

와… 개발자 완전 멋있다. 나도 장인 개발자가 되어서 새싹 개발자들 다 가르쳐주고 감동의 눈물 흘리게 해줘야지.

원대한 목표다! 그 정도 경지에 다다르려면 몇 년이 걸릴지 모르겠지만 배움을 게을리하지 않고 노력하면 언젠가 그렇게 될거라 기대하게 된다.

Categories:

Updated:

Leave a comment