본문 바로가기

코딩 규칙과 코드 검토

형식에 대한 합의와 동작에 대한 검토를 구별하고, 값의 뜻과 단위를 드러내는 이름과 이유를 적는 주석을 정합니다. 정원 검사의 비교 연산자 결함을 끝값 입력으로 드러내고, 고친 뒤 같은 입력을 회귀 사례로 남깁니다.

들여쓰기가 가지런하고 함수 이름이 친절해도, 정원이 찬 모임에 한 명을 더 받는 코드는 잘못된 코드입니다. 반대로 요구를 정확히 지키는 코드라도 읽기 어렵다면 다음에 고치는 사람이 결함을 만들기 쉽습니다. 코딩 규칙은 코드를 읽고 고치기 쉽게 하고, 코드 검토는 요구와 설계와 실제 동작 사이의 차이를 찾습니다. 이 글에서는 이름, 주석, 형식을 정하는 기준을 정리하고, 정원 검사의 결함 하나를 끝값 입력으로 찾아 고친 뒤 다시 확인하는 과정을 따라갑니다.

합의할 형식과 확인할 동작을 나눈다

코딩 규칙(coding convention)은 이름 짓기, 공백과 들여쓰기, 오류 처리 방식처럼 팀이 코드를 일관되게 쓰려고 정한 약속입니다. 프로젝트가 두 칸 들여쓰기를 정했다면 파일마다 다르게 쓰지 않으며, 이런 기계적인 차이는 자동 포매터(formatter)가 맞춰 줍니다. 린터(linter)는 쓰지 않는 변수나 위험한 문법처럼 미리 설정한 규칙을 어긴 곳을 찾아 주는 도구입니다.

하지만 어떤 도구도 요구의 뜻까지 이해해 주지는 않습니다. "마감 뒤에도 취소를 받는다"는 업무 규칙을 린터가 확인해 줄 수는 없으므로, 형식을 맞추는 일과 동작이 요구에 맞는지 확인하는 일을 구분해야 합니다.

길이에 대한 규칙도 마찬가지입니다. 원자료에 나오는 이름 31자, 파일 200줄, 함수 70줄 같은 숫자는 팀이 정할 수 있는 가이드일 뿐, 모든 언어가 지켜야 하는 한계가 아닙니다. 69줄짜리 함수가 전역 상태 다섯 개를 고친다면, 71줄이지만 계산만 하는 함수보다 낫다고 할 수 없습니다. 길이보다 그 단위가 맡은 일과 읽는 흐름을 보아야 하며, 팀이 정한 규칙과 언어의 실제 제약을 섞지 않습니다.

이름은 값의 뜻과 단위를 드러낸다

n과 c 대신 participantCount와 capacity라고 쓰면 무엇과 무엇을 비교하는지 바로 보입니다. hasAvailableSeat라는 이름은 참이나 거짓을 돌려주리라고 짐작하게 하고, register는 상태를 바꾸는 연산이라는 것을 드러냅니다. 그런데 이름은 조회처럼 보이는 getMembers가 실제로는 파일을 저장한다면, 이름과 동작이 어긋나 읽는 사람을 속이게 됩니다.

같은 개념에는 같은 이름을 씁니다. 모집 마감을 어떤 곳에서는 closed, 다른 곳에서는 finished나 done이라고 쓰면, 모임이 끝났다는 뜻인지 신청이 마감되었다는 뜻인지 헷갈립니다. 반대로 서로 다른 개념에는 다른 이름을 붙여, 앞 장에서처럼 모임의 상태는 status로, 요청의 결과는 code로 나눕니다.

변수의 이름과 업무에서 쓰는 식별 코드도 구별합니다. memberId는 코드 안의 변수 이름이고, member-42는 회원 한 명을 가리키는 데이터 값입니다. 업무 식별 코드를 정할 때는 값이 겹치지 않는지, 나중에 바뀔 수 있는지, 정렬 순서에 뜻이 있는지를 따져야 합니다. 회원의 이름이 바뀔 때 아이디까지 바뀌게 만들면 그동안의 참가 기록과 연결이 끊어집니다.

주석은 이유를 적는다

count += 1 바로 위에 "1을 더한다"고 적은 주석은 코드가 이미 말하는 것을 되풀이할 뿐입니다. 주석에는 코드만 보아서는 알 수 없는 이유를 적습니다.

function duplicateOrFull(session, memberId) {
  // R3: 이미 신청한 회원은 정원이 찼어도 FULL이 아니라 ALREADY_REGISTERED를 받는다.
  // 그래서 중복 검사를 정원 검사보다 먼저 한다.
  if (session.participants.includes(memberId)) return "ALREADY_REGISTERED";
  if (session.participants.length >= session.capacity) return "FULL";
  return null;
}

이 주석은 두 검사의 순서가 우연이 아니라 요구 R3에서 왔다는 것을 알려 주므로, 나중에 누군가 순서를 바꾸려 할 때 멈춰 생각하게 합니다. 다만 코드를 고치면서 주석을 그대로 두면 주석이 틀린 설명이 되므로, 검토할 때는 주석도 코드와 함께 읽습니다. 변경 이력과 작성자는 버전 관리 기록에 남기면 되지만, 라이선스나 출처처럼 남겨야 하는 표시는 마음대로 지우지 않습니다. 개인 정보나 비밀번호 같은 값은 예제 코드와 로그에 넣지 않으며, 이 교재의 회원 아이디는 모두 가상의 값입니다.

코드 검토를 시작하는 질문

코드 검토(code review)는 작성자가 아닌 사람이 코드를 읽고 문제를 찾는 활동입니다. 무엇을 볼지 정해 두면 검토가 취향에 대한 논쟁으로 흐르지 않습니다.

관점읽을 때의 질문확인하는 방법
요구정원에 이르면 새 신청을 막는가끝값 입력과 기대 결과
상태거절할 때 명단을 바꾸지 않는가호출 전후의 상태 비교
인터페이스결과 코드와 돌려주는 모양이 그대로인가호출하는 곳과 안내 문구 확인
오류저장 실패를 성공으로 보여 주지 않는가실패하는 경로 실행
이해함수 이름과 부수 효과가 맞는가이름, 입력, 출력을 말로 설명

검토 의견은 "이 코드 이상해요"보다, 문제를 다시 일으킬 수 있는 입력을 적는 편이 좋습니다. "정원 2, 현재 2명일 때 true를 돌려주어 세 번째 회원을 받습니다"처럼 쓰면, 작성자는 취향을 두고 다툴 필요 없이 요구를 어긴 상황을 재현하고 고칠 수 있습니다. 아래 그림은 이렇게 찾은 입력이 차이 확인, 수정, 회귀 확인으로 이어지는 과정을 보여 줍니다.

검토 의견을 재현 입력과 회귀 확인으로 연결한다

경계에서 새 신청을 실행한다. 같은 입력을 회귀 사례로 남겨 결함의 재발을 막는다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · CH4-4

검토 의견을 재현 입력과 회귀 확인으로 연결한다

형식 합의와 별도로 요구 위반을 구체적인 입력으로 확인합니다.

검토 의견을 재현 입력과 회귀 확인으로 연결한다요구의 기대값을 실행 결과와 비교하고 차이의 원인을 수정한 뒤 같은 입력을 회귀 확인에 남겨 검토를 반복합니다.요구와 기대값경계 입력을 고른다실행 결과기대와 실제를 비교한다차이의 원인조건 · 상태 · 계약 확인수정과 회귀 확인문제 입력을 검증에 남긴다

요구의 기대값을 실행 결과와 비교하고 차이의 원인을 수정한 뒤 같은 입력을 회귀 확인에 남겨 검토를 반복합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 반례 입력부터 수정과 회귀까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
반례 입력

경계에서 새 신청을 실행한다.

  • 반례·차이 1: capacity2
  • 반례·차이 2: count2
  • 반례·차이 3: 기대 거절
  • 경계 입력 재현
차이 찾기

true 반환으로 세 번째 회원을 받는 것은 요구 위반이다.

  • 반례·차이 1: 기대 거절
  • 반례·차이 2: 실제 true
  • 조건·상태·계약의 원인 검토
수정과 회귀

같은 입력을 회귀 사례로 남겨 결함의 재발을 막는다.

  • 반례·차이 1: count>=capacity이면 거절
  • 반례·차이 2: 실패 명단 유지 확인
  • 수정 후 경계 사례를 회귀에 추가

검토 의견을 재현 입력과 회귀 확인으로 연결한다

경계에서 새 신청을 실행한다. 같은 입력을 회귀 사례로 남겨 결함의 재발을 막는다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · CH4-4

검토 의견을 재현 입력과 회귀 확인으로 연결한다

형식 합의와 별도로 요구 위반을 구체적인 입력으로 확인합니다.

검토 의견을 재현 입력과 회귀 확인으로 연결한다요구의 기대값을 실행 결과와 비교하고 차이의 원인을 수정한 뒤 같은 입력을 회귀 확인에 남겨 검토를 반복합니다.요구와 기대값경계 입력을 고른다실행 결과기대와 실제를 비교한다차이의 원인조건 · 상태 · 계약 확인수정과 회귀 확인문제 입력을 검증에 남긴다

요구의 기대값을 실행 결과와 비교하고 차이의 원인을 수정한 뒤 같은 입력을 회귀 확인에 남겨 검토를 반복합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 반례 입력부터 수정과 회귀까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
반례 입력

경계에서 새 신청을 실행한다.

  • 반례·차이 1: capacity2
  • 반례·차이 2: count2
  • 반례·차이 3: 기대 거절
  • 경계 입력 재현
차이 찾기

true 반환으로 세 번째 회원을 받는 것은 요구 위반이다.

  • 반례·차이 1: 기대 거절
  • 반례·차이 2: 실제 true
  • 조건·상태·계약의 원인 검토
수정과 회귀

같은 입력을 회귀 사례로 남겨 결함의 재발을 막는다.

  • 반례·차이 1: count>=capacity이면 거절
  • 반례·차이 2: 실패 명단 유지 확인
  • 수정 후 경계 사례를 회귀에 추가

더 깊이: 승인할지 말지를 정하는 기준

검토가 길어지는 흔한 이유는 언제 승인해도 되는지에 대한 기준이 없어서입니다. Google이 공개한 코드 검토 지침의 The Standard of Code Review는 변경이 완벽하지 않더라도 코드베이스 전체의 건강을 분명히 나아지게 한다면 승인하는 쪽을 택하라고 정합니다. 완벽한 코드는 없고 더 나은 코드만 있다는 것이 그 이유입니다. 반대로 코드의 건강을 분명히 나빠지게 하는 변경은, 긴급한 상황이 아니라면 받아들이지 말라고 분명히 적어 둡니다.

같은 지침은 의견이 부딪힐 때의 순서도 정합니다. 기술적인 사실과 데이터가 의견이나 개인의 취향보다 앞서고, 형식에 관해서는 팀의 스타일 가이드가 최종 기준이며, 스타일 가이드에 없는 형식은 개인의 취향이므로 기존 코드에 맞춥니다. 꼭 고칠 필요는 없는 사소한 제안에는 앞에 Nit:를 붙여 작성자가 무시해도 된다는 것을 알리게 합니다. 이 글에서 검토 의견을 문제를 재현하는 입력으로 적으라고 한 것도, 의견을 데이터로 바꾸어 취향의 다툼을 줄이려는 같은 생각입니다.

정원 검사의 결함을 실행으로 드러낸다

검토 중에 다음 faultyHasSeat가 <=를 써서, 참가자 수가 정원과 같아도 자리가 있다고 판단한다는 의견이 나왔다고 합시다. 다음 코드를 reading-lab/examples/ch4-4.mjs로 저장하고 node examples/ch4-4.mjs로 실행해, 고치기 전과 고친 뒤를 같은 입력으로 비교합니다.

function faultyHasSeat(participantCount, capacity) {
  return participantCount <= capacity;
}

function hasSeat(participantCount, capacity) {
  return participantCount < capacity;
}

const reviewCases = [
  { count: 0, capacity: 2, expected: true },
  { count: 2, capacity: 2, expected: false },
  { count: 3, capacity: 2, expected: false },
];
for (const { count, capacity, expected } of reviewCases) {
  const before = faultyHasSeat(count, capacity);
  const after = hasSeat(count, capacity);
  console.log(count, capacity, "기대:", expected, "수정 전:", before, "수정 후:", after);
}
0 2 기대: true 수정 전: true 수정 후: true
2 2 기대: false 수정 전: true 수정 후: false
3 2 기대: false 수정 전: false 수정 후: false

for (const { count, capacity, expected } of reviewCases)는 배열의 각 객체에서 세 값을 꺼내 같은 이름의 변수에 담습니다. 빈 모임과 정원을 넘은 경우에는 두 구현이 같은 답을 내므로, 이 두 사례만 시험했다면 잘못된 연산자를 찾지 못했을 것입니다. 차이는 참가자 수가 정원과 정확히 같은 둘째 줄에서만 드러납니다. 이렇게 결함을 찾아낸 입력을 고친 뒤에도 테스트 사례로 남겨 두면, 나중에 같은 실수가 다시 들어올 때 바로 잡아낼 수 있습니다. 이전에 동작하던 기능이 변경 뒤에도 여전히 동작하는지 확인하는 시험을 회귀 테스트(regression test)라고 합니다.

이 작은 계산이 맞는다고 신청 기능 전체가 맞는 것은 아닙니다. 아이디 검사, 모집 상태, 중복, 거절 뒤의 상태도 각각 요구와 연결해 확인해야 합니다. 검토 기록에 무엇을 확인했는지 범위를 적어 두면, 조건 하나만 보고 기능 전체가 안전하다고 말하는 일을 줄일 수 있습니다.

의사코드와 실행 코드

복잡한 절차는 코드를 쓰기 전에 자연어와 조건문을 섞은 의사코드(pseudocode)로 먼저 설명할 수 있습니다. "형식이 틀린 아이디면 거절, 마감이면 거절, 이미 신청했으면 상태를 그대로 돌려준다"처럼 적는 것입니다. 의사코드는 설계를 설명하는 글이지 실행할 수 있는 문법이 아니며, 분기가 많으면 순서도로, 끝값 입력은 표로 함께 보여 줄 수 있습니다.

의사코드를 실제 코드로 옮길 때는 그 언어의 비교, 복사, 예외 규칙을 확인해야 합니다. 또 "한 줄씩 실행하면 인터프리터 언어, 미리 모두 바꾸면 컴파일 언어"처럼 언어를 둘로만 나누어 외우지 않습니다. 같은 언어라도 실행 환경이 여러 처리 방식을 섞어 쓰기 때문이며, 이 교재는 Node.js에서 JavaScript를 실행한 결과로 동작을 확인합니다.

검토 과정을 기록한다

검토를 기록할 때는 문제를 일으킨 입력, 기대한 결과, 실제 결과, 고친 이유, 확인한 범위를 남깁니다. 앞의 예에서는 참가자 수가 정원과 같은 입력이 문제였고, 기대는 새 신청의 거절, 실제는 자리가 있다는 판단이었으며, <=를 <로 고쳤습니다. 그리고 빈 모임과 정원을 넘은 경우도 다시 확인했다는 것까지 적습니다.

한 사람이 검토에서 모든 실수를 찾으리라고 기대하지 않습니다. 요구를 모르는 사람에게 코드를 읽혀 이름만으로 뜻을 알 수 있는지 확인하는 방법도 있습니다. 사람의 검토와 자동 검사는 찾는 문제가 서로 다르므로 함께 씁니다.

정리

  • 코딩 규칙과 포매터, 린터는 형식을 맞추고 위험한 문법을 찾아 주지만, 동작이 요구에 맞는지는 따로 확인해야 합니다.
  • 이름은 값의 뜻과 단위를 드러내야 하며, 같은 개념에는 같은 이름을, 다른 개념에는 다른 이름을 씁니다.
  • 주석에는 코드가 이미 말하는 것이 아니라 코드만으로 알 수 없는 이유를 적고, 코드를 고칠 때 함께 고칩니다.
  • 검토 의견은 문제를 다시 일으킬 수 있는 입력과 기대 결과, 실제 결과로 적습니다.
  • 결함을 찾아낸 입력은 고친 뒤에도 회귀 사례로 남겨 같은 실수가 다시 들어오는 것을 막습니다.

연습 문제

1. 할인 조건 검토

동아리 굿즈는 5개 이상 사면 10% 할인합니다. 다음 코드를 검토해, 뜻이 드러나는 이름을 제안하고 끝값 사례 세 개와 기대 결과를 적으세요.

const d = n > 5 ? 0.1 : 0;
해설 보기

d는 할인 금액인지 비율인지 여부인지 알 수 없으므로, 수량을 받아 할인 비율을 돌려주는 discountRate(quantity) 같은 함수로 바꾸고 본문은 quantity >= 5 ? 0.1 : 0으로 씁니다. 끝값 사례는 수량 4에 0, 5에 0.1, 6에 0.1이며, 원래 코드는 수량 5에서 0을 돌려주어 요구의 "이상"을 "초과"로 잘못 옮겼습니다. 고친 이유는 "5개 이상 할인이라는 요구에 맞도록 수량 5도 할인에 포함한다"처럼 한 문장으로 적습니다. 코드가 짧다는 것만으로는 요구에 맞는지도, 읽기 쉬운지도 알 수 없습니다.

2. 재현할 수 있는 검토 의견

다음 cancel을 검토하던 사람이 "이 코드 좀 이상해요"라는 의견을 남겼습니다. 무엇이 요구를 어기는지 찾고, 작성자가 바로 재현할 수 있는 검토 의견으로 고쳐 쓰세요.

function cancel(session, memberId) {
  if (!session.participants.includes(memberId)) {
    return { ok: false, code: "NOT_REGISTERED", state: session };
  }
  const participants = session.participants.filter((id) => id !== memberId);
  return { ok: true, code: "CANCELLED", state: { ...session, status: "OPEN", participants } };
}
해설 보기

취소한 뒤의 상태에 status: "OPEN"을 넣어, 마감된 모임에서 누군가 취소하면 모집이 다시 열립니다. 이는 마감 여부를 그대로 두어야 한다는 요구 R6을 어깁니다. 검토 의견은 "정원 2인 모임에 min과 hana가 신청한 뒤 마감했습니다. min이 취소하면 결과는 CANCELLED이지만 상태가 OPEN으로 바뀌어 새 신청을 받게 됩니다. R6에 따라 상태는 CLOSED로 남아야 합니다."처럼 쓸 수 있습니다. 고칠 때는 status를 덮어쓰지 않도록 그 부분을 지우고, 마감 뒤 취소 사례를 회귀 사례로 남깁니다.

3. 뜻을 담은 아이디

회원 아이디를 kim-2026-cs처럼 성, 입학 연도, 학과로 만들자는 제안이 나왔습니다. 이 제안을 검토하세요.

해설 보기

아이디만 보고 회원의 정보를 알 수 있어 편해 보이지만, 학과를 옮기거나 이름이 바뀌면 아이디의 뜻이 낡거나 아이디를 바꿔야 하는 문제가 생깁니다. 아이디를 바꾸면 그동안의 참가 기록과 연결이 끊어지고, 같은 성과 연도와 학과를 가진 회원이 여럿이면 값이 겹칩니다. 또 아이디에 개인 정보가 드러나므로 명단을 보여 줄 때도 조심해야 합니다. 회원을 구별하는 아이디는 바뀌지 않는 값으로 두고, 학과나 입학 연도는 따로 속성으로 저장하는 편이 낫습니다.

이 장에서는 모듈을 나누는 기준, 모듈 사이의 의존, 인터페이스와 행동, 그리고 코드 검토를 차례로 다루었습니다. 다음 장의 테스트 사례와 오라클에서는 정상, 끝값, 거절 사례를 체계적으로 골라 테스트를 만드는 방법을 다룹니다.


원자료와 더 읽기

장원사이버평생교육원 「소프트웨어공학」 PDF 물리 201–217쪽의 주제를 바탕으로 설명, 도표, 실습을 새로 구성했습니다. 원자료의 고정된 길이 규칙은 모든 언어의 제한으로 옮기지 않았습니다. 언어별 공개 구조는 MDN JavaScript 모듈과 클래스를 참고합니다.