코드를 빛나게 하는 개발자의 작은 습관 7가지
컨텐츠 정보
- 조회 448
본문
최근 필자는 반짝이도록 포크를 닦는 행위와 좋은 코드를 작성하는 것의 관계에 대해 이야기했다. 그리고 개발자로서 세밀함에 집중하는 태도야말로 진정한 탁월함으로 가는 열쇠라는 점을 강조했다. 그 칼럼을 쓰고 나니, 코드를 ‘미슐랭 스타’ 수준으로 다듬을 수 있는 방법은 무엇일지 생각하게 됐다.
여기서는 평범한 코드를 훌륭한 코드로 끌어올려줄 일곱 가지 작은 습관을 소개한다.
상수를 쓰지 말고 열거형을 사용하라
의미는 암시가 아니라 명시적으로 표현해야 한다.
코드에서는 상수 값보다 열거형(enumeration)을 사용하는 것이 훨씬 낫다. 열거형은 컴파일러 친화적일 뿐 아니라 읽기도 훨씬 쉽다. 예를 들어 아래 코드를 보자.
function calculateDiscount(customerId: number): number { // Look up loyalty tier somehow... if (customerId === 100) { return 1; } else if (customerId === 200) { return 2; } else { return 3; }}위 코드에서 1, 2, 3이 무엇을 의미하는지 전혀 알 수 없다. 그래서 “좋아, 값에 이름을 붙이자”라는 생각으로 다음과 같이 고칠 수 있다.
const DISCOUNT_GOLD = 1;const DISCOUNT_SILVER = 2;const DISCOUNT_BRONZE = 3;function calculateDiscount(customerId: number): number { if (customerId === 100) { return DISCOUNT_GOLD; } else if (customerId === 200) { return DISCOUNT_SILVER; } else { return DISCOUNT_BRONZE; }}확실히 전보다는 나아졌다. 하지만 이 코드 역시 혼동을 불러올 수 있다. 함수가 45를 반환하도록 변경되더라도, 45가 무슨 의미인지 정의되지 않을 수 있기 때문이다.
반면, 열거형을 정의하면 함수는 반드시 열거형을 반환해야 하므로 모호성이 사라진다. 따라서 다음 코드는 더 이상 해석의 여지가 없다.
enum DiscountTier { Gold, Silver, Bronze,}function calculateDiscount(customerId: number): DiscountTier { if (customerId === 100) { return DiscountTier.Gold; } else if (customerId === 200) { return DiscountTier.Silver; } else { return DiscountTier.Bronze; }}설명 변수를 반드시 사용하라
이름 하나가 이해를 한 단계 줄여줄 수 있다면, 그 이름을 붙여라.
이 기법은 필자가 특히 강조하고 싶은 부분이다.
function logPurchase(userId: number, itemId: number): void { console.log(`User ${userId} purchased item ${itemId}`);}logPurchase(getUserId(), getItemId());위 코드를 보면 logPurchase 호출이 두 개의 함수 호출을 인자로 받고 있다. 문제는, 인자가 어떤 값인지 알기 위해서는 각각의 함수 안으로 들어가 봐야 한다는 점이다. 이렇게 작성하면 1년 뒤 이 코드를 읽는 개발자 입장에서는 매우 불친절하다.
따라서 항상 설명 변수(explaining variable)를 사용해야 한다. 코드를 한 줄씩 따라가는 미래의 유지보수자가 쉽게 이해할 수 있도록 말이다.
const userId = getUserId();const itemId = getItemId();logPurchase(userId, itemId);이 방식은 유지보수성과 디버깅 친화적이다. 그리고 잊지 말아야 할 사실이 있다. 그 유지보수자가 바로 미래의 자신일 수도 있다.
에러 메시지는 구체적이고 자세하게 작성하라
스스로를 설명하는 에러는 절반은 해결된 것이나 다름없다.
코드를 작성하면서 가장 짜증 나는 상황은 다음과 같은 에러 메시지를 만나는 것이다.
List index out of bounds끝. 이게 전부다. 어떤 리스트인지? 어떤 인덱스 값인지? 전혀 알 수 없다.
하지만 오늘날의 코드가 제공하는 자기 설명성과 디테일을 생각해 본다면, 다음과 같은 메시지를 주는 게 훨씬 낫다.
The list named 'menuItemModifiers' attempted to retrieve index 43, but there are only 42 items in the list이 메시지는 훨씬 도움이 되고, 사실 코드로 작성하는 것도 그렇게 어렵지 않다.
로그를 아끼지 말라
로그는 값싸지만, 로그에서 얻는 인사이트는 그 무엇보다 소중하다.
앞에서 말한 맥락과 마찬가지로, 로그를 남길 때 ‘과하다’ 싶을 정도로 기록하는 것을 두려워하지 말아야 한다. 완전하고 상세한 로그 파일은 빈약하고 단편적인 로그보다 훨씬 가치가 크다. 게다가 로그 분석 도구는 데이터를 나누고, 필터링하고, 재구성하도록 설계돼 있다. 그러니 로그 레벨을 적극 활용하고, 풍부한 데이터와 충분한 설명을 담아 기록하는 것을 망설일 필요가 없다.
열었으면 닫아라
로직을 짜기 전에 습관을 먼저 만들어야 한다.
무언가를 생성하거나 열었다면, 즉시 그것을 닫거나 해제하는 코드를 작성하는 습관을 들여야 한다. 급하게 코드를 작성하다 보면 이를 잊어버리기 쉽다. 예를 들어 다음 코드를 보자.
function readFirstLine(filePath: string): string { const file = fs.openSync(filePath, "r"); // File handle opened const buffer = Buffer.alloc(100); fs.readSync(file, buffer, 0, 100, 0); // Oops: no close! // The file handle stays open until the process ends. return buffer.toString().split("n")[0];}위 코드는 파일을 열어놓고 닫지 않아서 프로세스가 끝날 때까지 파일 핸들이 열린 상태로 남게 된다. 이는 매우 바람직하지 않다. 따라서 openSync를 작성하는 순간, 즉시 try…finally 블록을 추가하는 습관을 가져야 한다.
function readFirstLine(filePath: string): string { const file = fs.openSync(filePath, "r"); try { // don't put anything here until the finally clause is written } finally { // Always close what you open fs.closeSync(file); }}이것은 꼭 길러야 할 훌륭한 습관이다. 무언가를 할당했다면, 반드시 바로 해제하는 코드까지 작성하라. while 루프를 작성할 때도 로직을 넣기 전에 반드시 탈출 조건을 마련하라. 이런 과정을 반복하며 머슬 메모리처럼 몸에 익혀야 한다.
계산할 수 있는 값은 저장하지 마라
가공된 데이터는 시간이 지날수록 신뢰성을 잃는다.
데이터베이스에는 반드시 원시 데이터(raw data)만 저장하고, 계산된 데이터는 저장하지 않는 것이 원칙이다. 예를 들어 아래 테이블의 TotalPrice 필드는 완전히 불필요하다.
CREATE TABLE Orders ( OrderID INT PRIMARY KEY, Quantity INT NOT NULL, UnitPrice DECIMAL(10,2) NOT NULL, TotalPrice DECIMAL(10,2) NOT NULL -- ❌ Calculated field stored);데이터베이스에 TotalLessFeesAndTax 같은 저장된 필드가 들어 있는 것을 보면 미칠 지경이다. 아이템, 수수료, 세금 값만 저장하고 나머지는 계산해서 사용하라. 그래야 어느 한 값이 바뀌더라도 계산된 값이 낡아버리는 문제를 피할 수 있다.
변경 가능성을 최소화하라
바뀌지 않는 값은 나중에 당신을 속이지 않는다.
무언가를 불변(immutable)으로 만들 수 있다면, 반드시 그렇게 하라. 애초에 바뀌지 않아야 할 값이 예상치 못하게 바뀌는 것만큼 디버깅을 지루하고 고통스럽게 만드는 일도 드물다. 변화할 수 없도록 만들어버리면, 문제를 처음부터 차단할 수 있다. 예를 들어 다음 코드를 보자.
function moveRight(p: Point, distance: number): void { // Mutates the original object p.x += distance;}const start: Point = { x: 0, y: 0 };newPoint = moveRight(start, 5);moveRight의 호출자는 아마도 start가 바뀌리라고는 예상하지 않았을 것이다. 사실 바뀌어서는 안 된다. 하지만 방어적으로 다음과 같이 코드를 작성할 수 있다.
interface Point { readonly x: number; readonly y: number;}이제 컴파일러가 나중에 미묘하게 숨어 있다가 당신을 괴롭힐 버그를 미리 찾아낸다.
좋은 코드를 작성한다는 것은 세밀한 주의와 강한 의지, 꾸준한 자기 규율을 요구한다. 하지만 우리 모두에게 그런 자원이 항상 넉넉한 것은 아니다. 그래서 훌륭한 개발자는 언제나 좋은 습관을 들이고, 체계적인 코딩 방식을 만들어간다. 여기서 소개한 7가지 습관을 익히면 훗날 큰 도움이 될 것이다.
dl-itworldkorea@foundryco.com
관련자료
-
링크
-
이전
-
다음






