// education

프로그래밍과 코드 검사

설계의 마지막 걸음을 코드로 옮기기

15장까지 다룬 설계 단계는 "무엇을 만들 것인가"를 점점 더 구체적인 도면으로 바꾸는 과정이었다. 요구사항이 아키텍처가 되고, 아키텍처가 모듈 단위의 상세설계가 되고, 상세설계는 마지막으로 컴퓨터가 실행할 수 있는 코드가 된다. 이 마지막 걸음이 프로그래밍이다.

프로그래밍을 "드디어 진짜 개발이 시작되는 단계"라고 여기기 쉽지만, 소프트웨어 공학의 관점에서는 오히려 정반대에 가깝다. 상세설계가 제대로 되어 있다면 프로그래밍은 그 설계를 정해진 문법으로 옮겨 적는 상대적으로 기계적인 작업이 된다. 책마루 프로젝트의 "재고 차감" 모듈을 예로 들면, 상세설계 단계에서 이미 "주문이 확정되면 해당 상품의 재고 수량에서 주문 수량만큼 뺀다. 재고가 부족하면 주문을 거부한다"는 절차와 입출력이 정해져 있어야 한다. 프로그래머가 할 일은 이 절차를 파이썬이든 자바스크립트든 정해진 언어의 문법으로 정확하게 표현하는 것이다. 설계가 흔들린 채로 코딩에 들어가면, 프로그래머는 코드를 짜면서 동시에 설계 결정까지 내려야 하고 이는 나중에 팀원마다 서로 다른 방식으로 같은 문제를 풀어버리는 결과로 이어진다.

이번 장에서는 이 코딩 단계에서 지켜야 할 두 가지 축을 다룬다. 하나는 "어떤 코드가 좋은 코드인가"를 정하는 코딩 컨벤션이고, 다른 하나는 그 코드가 실제로 기준을 지켰는지 사람이 직접 들여다보는 코드 검사다.

프로그래밍 언어의 선택도 설계의 연장이다

프로그래밍 언어는 사람의 의도를 컴퓨터가 실행 가능한 형태로 옮기는 매개체다. 언어는 지난 수십 년간 "기계에 더 가까운 표현"에서 "사람의 생각에 더 가까운 표현"으로 계속 진화해 왔고, 이 흐름을 보통 세대로 구분한다.

세대 특징 예시
1세대 기계어·어셈블리어. 하드웨어를 직접 다룸 기계어, 어셈블리
2세대 최초의 고급언어. 절차를 사람이 읽을 수 있는 문장으로 표현 FORTRAN, COBOL
3세대 구조적 프로그래밍을 지원, 제어 흐름을 블록으로 관리 C, Pascal, C++
4세대 절차보다 원하는 결과를 선언하는 데 초점 (비절차 언어) SQL, 질의어 계열

이 표에서 눈여겨볼 지점은 세대가 올라갈수록 "어떻게 계산할지"보다 "무엇을 원하는지"를 표현하는 데 가까워진다는 것이다. 책마루 시스템에서 재고 조회 화면을 만든다고 하면, 3세대 언어로는 "재고 목록을 하나씩 순회하며 카테고리가 일치하는 것만 골라 새 목록에 담는" 절차를 직접 써야 하지만, SQL 같은 4세대 언어(비절차 언어)로는 SELECT * FROM stock WHERE category = '문구'처럼 원하는 결과만 선언하면 된다. 어떤 언어를 선택할지는 프로젝트 성격에 달려 있다 — 책마루처럼 재고·주문 데이터를 다루는 시스템은 데이터 질의에 4세대 언어를, 화면과 서버 로직에는 3세대 계열 언어를 함께 쓰는 것이 일반적이다.

언어 선택 자체도 사실 설계 결정의 연장이다. 객체지향 언어를 쓰기로 했다면, 책마루의 "상품"과 "주문"을 각각 클래스로 묶어 데이터와 그 데이터를 다루는 동작을 한 단위로 캡슐화할 수 있다 — 이건 단순히 문법을 고르는 문제가 아니라 시스템을 어떤 단위로 쪼개서 생각할지를 정하는 일이다. 언어가 제공하는 개념(캡슐화, 상속, 다형성 같은)을 제대로 이해하지 못한 채 문법만 흉내 내면, 객체지향 언어를 쓰면서도 절차 지향적으로 사고하는 어정쩡한 코드가 나온다.

좋은 코드의 세 가지 기준

"좋은 프로그래밍 스타일"을 한 문장으로 정의하기는 어렵지만, 실무에서 반복적으로 확인되는 공통점은 있다 — 좋은 코드는 나중에 그 코드를 처음 보는 사람도 설계 문서 없이 빠르게 의도를 파악할 수 있는 코드다. 이 기준을 세 갈래로 나누어 보면 문서화, 간결성, 명확성이다.

코드의 문서화

코드 자체가 하나의 문서라는 발상이 낯설게 느껴질 수 있지만, 실제로 프로그램이 다 만들어진 뒤에는 상세설계서를 다시 펼쳐보는 일보다 코드를 직접 읽는 일이 훨씬 잦다. 그래서 코드는 그 자체로 읽혔을 때 의도가 드러나야 한다.

가장 기본적인 장치는 식별자 이름이다. 책마루 재고 차감 함수를 f1(a, b)라고 짓는 것과 deductStock(productId, quantity)라고 짓는 것은 실행 결과는 같아도 유지보수 비용은 전혀 다르다. 이름 자체가 "이 함수는 상품 ID와 수량을 받아 재고를 줄인다"는 설명을 대신하기 때문이다.

두 번째 장치는 주석이다. 좋은 주석은 모듈의 목적, 호출 방법, 인자의 의미, 그리고 이 코드를 언제 누가 작성·수정했는지를 담는다. 다만 주석의 함정도 분명하다 — stock -= qty // 재고에서 수량을 뺀다처럼 코드를 그대로 한국어로 옮겨 적는 주석은 아무 정보도 더하지 않는다. 좋은 주석은 코드만 봐서는 알 수 없는 맥락, 이를테면 "왜 여기서 재고를 락(lock)으로 감싸야 하는지"를 설명해야 한다.

왜 이걸 굳이 신경 써야 하나: 책마루 시스템을 처음 만든 개발사가 아니라 6개월 뒤 다른 개발사가 유지보수를 맡게 되는 상황을 가정해보자. 원래 작성자에게 물어볼 수 없는 상태에서, 코드와 주석만으로 로직을 파악해야 한다면 문서화 수준이 곧 유지보수 비용을 결정한다.

코드의 간결성

간결성은 "짧게 쓰라"는 뜻이 아니라 "불필요한 잡음을 없애라"는 뜻에 가깝다. 들여쓰기와 공백을 일관되게 사용해 실행문 블록과 주석을 눈으로 즉시 구분할 수 있게 하는 것, 복잡한 조건식을 괄호로 명확히 묶는 것, 한 줄에는 한 문장만 쓰는 것 같은 관례가 여기에 속한다. 예를 들어 책마루의 배달 가능 여부 판단 로직을 한 줄에 여러 조건과 대입을 욱여넣으면 당장은 짧아 보여도, 나중에 조건 하나를 수정할 때 어디까지가 어떤 조건에 걸리는지 다시 해독해야 하는 비용이 생긴다.

전역 변수를 피하고 함수의 책임 범위를 좁게 유지하는 것도 간결성의 일부다. 책마루 시스템에서 재고 수량을 전역 변수로 여기저기서 직접 건드리게 하면, 재고가 예상과 다르게 바뀌었을 때 어느 코드가 언제 건드렸는지 추적하기 어려워진다. 대신 재고를 다루는 함수를 통해서만 값을 변경하도록 좁혀두면 변경 지점이 하나로 모여 추적과 디버깅이 쉬워진다. 이런 관례를 코딩 표준(coding standard)이라 부르고, 표준을 지키는 목적은 결국 프로그램 관리를 쉽게 하고 여러 사람이 함께 작업할 때의 생산성을 지키는 데 있다.

코드의 명확성

명확성은 조건문과 함수 구조가 읽는 사람의 예상과 어긋나지 않아야 한다는 원칙이다. 예를 들어 조건문에는 반드시 예외 상황까지 처리되어야 한다 — 책마루의 재고 차감 로직에서 "재고가 충분한 경우"만 다루고 "재고가 정확히 0이 되는 경계", "음수 요청이 들어오는 경우" 같은 예외를 빠뜨리면 코드는 정상 흐름에서는 잘 작동하다가 특정 상황에서만 조용히 오작동한다.

함수를 설계할 때도 몇 가지 스스로 던져야 할 질문이 있다. 이 함수는 너무 길거나 짧지 않은가, 인자 수가 지나치게 많지 않은가, 목적이 하나로 명확한가. 책마루의 processOrder() 같은 함수가 재고 확인, 결제 처리, 배달 등록, 알림 발송까지 한 번에 다 하고 있다면 이는 함수 하나가 너무 많은 책임을 지고 있다는 신호다. 각 책임을 별도 함수로 쪼개면 코드를 읽는 사람이 "지금 이 부분이 무슨 일을 하는 코드인가"를 함수 이름만으로 짐작할 수 있게 된다.

코드 검사: 품질을 개인이 아니라 조직이 책임진다

코딩 컨벤션이 "어떻게 써야 하는가"에 대한 기준이라면, 코드 검사(code inspection)는 "그 기준이 실제로 지켜졌는가"를 확인하는 절차다. 소프트웨어 개발 비용 중 상당 부분은 오류를 찾고 고치는 데 들어가는데, 이 오류를 발견하는 시점이 이를수록 수정 비용이 낮아진다는 점은 1장에서 이미 짚었다. 코드 검사는 바로 이 "이른 발견"을 코딩 직후, 즉 테스트로 실행해보기 전 단계에 배치하는 활동이다.

코드 검사가 중요한 이유는 단순히 오류를 잡아내는 것을 넘어선다. 검사에 참여한 여러 사람이 한 사람이 짠 코드의 로직을 함께 이해하게 되면서, 그 코드에 대한 지식이 한 사람에게만 갇혀 있지 않게 된다. 책마루 시스템처럼 소규모 팀이 운영하는 프로젝트에서는 특정 개발자가 휴가나 퇴사로 자리를 비웠을 때 "이 코드는 이 사람만 안다"는 상황이 가장 위험한데, 코드 검사는 이런 지식 편중을 완화하는 부수 효과가 있다.

검사팀의 구성과 역할

코드 검사는 즉흥적인 잡담이 아니라 정해진 순서와 역할에 따라 진행되는 공식 활동이다. 새로 작성되었거나 많은 부분이 변경된 코드가 검사 대상이 되고, 보통 4~7명 정도로 검사팀을 꾸린다. 역할은 다음과 같이 나뉜다.

역할 하는 일
저자(author) 코드를 작성한 사람. 코드에 대한 배경 정보를 제공하고 질문에 답한다
사회자(moderator) 검사 회의를 진행하는 의장. 논의가 코드 자체에서 벗어나지 않게 조율한다
낭독자(reader) 코드를 한 줄씩 또는 한 단락씩 읽으며 그 의미를 풀어 설명한다
검사자(inspector) 사전에 코드를 미리 읽고 의문점과 문제점을 파악해 온다
기록자(recorder) 논의 중 발견된 문제점을 기록하고 검사 결과를 문서로 정리해 배포한다

한 가지 원칙이 눈에 띈다 — 관리자는 검사 회의에 참석하지 않는 것이 바람직하다는 것이다. 코드 검사의 목적이 오류를 찾아 품질을 높이는 것이지, 코드를 작성한 사람의 업무 능력을 평가하는 자리가 아니기 때문이다. 검사 결과를 인사평가 자료로 쓰기 시작하면, 참여자들은 문제를 솔직하게 드러내기보다 방어적으로 굴게 되고 검사는 형식적인 절차로 전락한다.

진행 방식과 오류의 분류

검사 참여자들은 회의 이전에 미리 코드를 읽고 의문점을 정리해 온다. 회의에서는 낭독자가 코드를 읽어나가고 검사자들이 문제를 지적하는데, 이때 중요한 원칙은 "오류를 찾아 지적하는 것"이지 "그 자리에서 해결책까지 논의하는 것"이 아니라는 점이다. 해결 방법을 찾는 논의는 시간을 무한정 늘리고, 검사 회의의 초점을 흐린다 — 해결은 저자가 회의 이후에 따로 맡는다.

발견된 문제는 기록자가 문서로 남긴다. 이때 오류를 몇 가지 축으로 분류해두면 나중에 통계를 내어 "이 팀이 반복적으로 어떤 유형의 실수를 하는가"를 파악하는 데 쓸 수 있다.

오류 유형 의미
데이터 오류 자료형, 변수 선언, 매개변수 관련 오류
문서 오류 부적절하거나 잘못된, 혹은 빠진 주석
기능 오류 모듈이 원래 해야 할 일(what)을 잘못 수행
논리 오류 수행 방법(how)이 잘못됨
성능 오류 요구되는 효율(응답 속도 등)을 충족하지 못함
표준 오류 합의된 코딩 표준을 따르지 않음

여기에 더해 오류가 "빠졌는지(missing)", "잘못됐는지(wrong)", "불필요하게 들어갔는지(extra)"까지 구분하면, 책마루 재고 차감 함수의 검사 기록은 예를 들어 이런 식으로 남을 수 있다 — "재고가 0 미만으로 내려가는 경우에 대한 예외 처리가 빠짐(기능 오류, missing)", "재고 잠금 해제 코드가 잠금 코드보다 먼저 실행될 수 있는 순서로 짜여 있음(논리 오류, wrong)".

코드 검사 이후

검사에서 나온 지적 사항은 저자가 수정한 뒤 사회자에게 재확인을 받는다. 이 재확인의 목적은 "지적된 문제가 실제로 고쳐졌는가"와 "고치는 과정에서 새로운 문제가 생기지 않았는가"를 함께 보는 것이다. 검사가 습관으로 자리 잡은 팀에서는 코드 검사가 개발 라이프사이클의 정식 단계로 편입되고, 이는 품질 보증(SQA) 활동의 일부로 인식된다 — 즉 품질은 배포 직전에 몰아서 확인하는 것이 아니라, 코드가 만들어지는 매 순간 조직적으로 관리하는 것이라는 관점이다.

핵심 정리

코드 검사가 오류를 사람의 눈으로 걸러내는 정적인 점검이라면, 다음 장(17장)에서 다룰 소프트웨어 테스트는 프로그램을 실제로 실행시켜 오류를 찾아내는 동적인 점검이다. 책마루 재고 차감 함수를 예로, 단위 시험부터 인수 시험까지 네 층위의 테스트와 블랙박스·화이트박스 테스트 기법을 자세히 다룬다.

← 이전사용자 인터페이스 설계와 상세설계 다음 →소프트웨어 테스트