더 좋은 코드를 쓰는, 더 나은 개발자가 되기 위해 오늘부터 바로 시작할 수 있는 몇 가지 습관과규칙을 공유한다. 지난 몇 년 동안 필자가 코드를 쓰면서 후회하는 과정에서 얻은 귀중한 교훈이다. 결국 필자가 아는 최악의 코더는 6개월 전의 자신이다.
디테일에 집착하자
지루하고 싫증나는 일을 좋아하는 사람은 없다. 하지만 유능한 고참 개발자는 직접 쓴 코드베이스가 일관적인 방식으로 동작하기 위해 지루한 작업을 마다하지 않는다. 뛰어난 고참 개발자는 명확하고 이해하기 쉽고 합리적인 규칙을 세워서 그 규칙을 철저히 지킨다. 들여쓰기에 극히 민감하며 린터 또는 코드 포매터를 사용해 코드의 서식을 일관적으로 유지한다. 변수의 대소문자 표기에 민감한데, 심지어 사용하는 언어가 대소문자를 구분하지 않는 경우에도 마찬가지다. 또한 필수가 아니더라도 문에는 항상 중괄호를 사용한다.기본적으로 뛰어난 고참 개발자는 작은 부분에 신경을 쓰면 큰 부분은 알아서 해결되는 경우가 많다는 것을 잘 안다.
좋은 이름 짓기
오랜 우스개로, "소프트웨어 개발에서 두 가지 어려운 점은 캐시 무효화, 이름 짓기, 그리고 숫자 하나 차이의 오류"라는 말이 있다. 이름 짓기는 그만큼 어렵지만, 나쁜 이름을 피하는 방법을 아는 것만으로 대부분의 문제는 피할 수 있다.우선 입력하는 번거로움을 줄이기 위해 짧은 이름을 선택하는 것은 아주 잘못된 방법이다. 약어로 된 짧은 변수 이름은 터미널 창의 너비가 80자였던 과거의 흔적이다. 이제는 gw가 아니라 grossWeight, nw가 아닌 netWeight을 사용해야 한다. 이렇게 해도 아무런 불이익도 없다. 번거로운 입력이 걱정된다면 인텔리센스를 활용하면 된다.
약어를 사용하지 않는 것과 함께, 최대한 완전한 형태의 이름이 되도록 해야 한다. lengthInCentimeters라는 더 완전한 이름을 지을 수 있는데 그냥 length라고 할 이유가 없다.
변수의 목적을 정확히 설명하는 길고 명확한 변수 이름에는 아무런 단점도 없다.
항상 설명 변수를 사용하기
초보 코더는 지름길을 찾고, 심지어 자신이 지름길을 찾는다는 사실조차 인지하지 못하는 경우도 있다. 그래서 다음과 같은 코드를 쓰곤 한다.function checkAccess(userRole: string, isAuthenticated: boolean, isAdmin: boolean, hasSubscription: boolean, isGuest: boolean): string {
return ((isAuthenticated && userRole === "member") ||
(isAdmin && !hasSubscription) ||
(isGuest && !isAuthenticated && userRole === "guest") ||
(isAuthenticated && userRole === "premium" && hasSubscription))
}
```
별로 똑똑하지 않은 필자의 머리로는 두 개가 넘는 부울 식을 한 번에 추적하기가 어렵다. 머릿속으로 생각해서 가장 먼저 떠오르는 것을 써내려가지 말고, 단계별로 각 부울 식에 이름을 지정하고 하나의 값으로 묶은 다음 if 문에 사용하는 것이 훨씬 더 나은 방법이다.
function checkAccess(userRole: string, isAuthenticated: boolean, isAdmin: boolean, hasSubscription: boolean, isGuest: boolean): string {
const isMemberAndAuthenticated = isAuthenticated && userRole === "member";
const isAdminWithoutSubscription = isAdmin && !hasSubscription;
const isGuestAndNotAuthenticated = isGuest && !isAuthenticated && userRole === "guest";
const isPremiumMemberWithSubscription = isAuthenticated && userRole === "premium" && hasSubscription;
const hasAccess = isMemberAndAuthenticated ||
isAdminWithoutSubscription ||
isGuestAndNotAuthenticated ||
isPremiumMemberWithSubscription;
return hasAccess;
}
```
위와 같이 설명 변수를 사용하면 전체 부울 식을 훨씬 더 쉽게 따라갈 수 있다. 또한 설명 변수를 사용하면 디버거에서 이런저런 요소를 알아보기도 더 쉽다.
항상 한 번에 한 단계씩 실행하고 설명 변수를 사용해서 명확하고 이해하기 쉬운 코드를 만들어야 한다.
추상화에 맞춘 코딩
좋은 코드 쓰기의 관건은 기능성이다. 뛰어난 고참 개발자는 어떤 일을 수행하는 방식이 항상 더 개선될 수 있다는 사실을 알고, 따라서 구체적인 구현 대신 일종의 추상화에 맞춰 코딩한다.“급진적인 새로운 구현”은 언제든 발견될 수 있는데 구체적인 구현에 너무 얽매이는 경우 그 새로운 솔루션을 활용하기 어렵다는 개념이다.
인터페이스를 정의하고 그에 따라 코딩하면 새로운 구현을 놓치지 않고 간단히 활용할 수 있다.
한 번에 하나의 변경만 수행하고 테스트하기
몇 번의 시행착오를 겪어보면 결국 두 개 이상을 변경하고 새로운 코드를 테스트해서 문제가 발생하면 어느 변경이 그 문제를 유발했는지 확실히 알 수 없다는 사실을 알게 된다.현명한 개발자는 한 번에 하나씩, 느리고 신중하게 작업한다는 개념에 따라 한 번에 한 가지만 체계적으로 변경하면서 이 하나의 변경이 미치는 영향을 확인하고 다음 단계로 진행한다. 방대한 상호종속성이 존재하는 대규모 코드베이스에서 특히 중요하다. 어느 한 곳에서의 변경이 전혀 다른 곳에서 예상치 못한 영향을 미칠 수 있기 때문이다. 예상치 못한 일이 발생하면 어느 변경이 그 문제를 일으켰는지 즉시 알 수 있어야 한다.
어떤 작업이든 한 곳에서만 수행되도록 코딩하기
좋은 코드를 유지∙관리하는 사람은 3개의 INSERT 문 중에서 데이터베이스를 업데이트하는 문이 무엇인지 알아내느라 고생할 필요가 없다. 그러한 종류의 작업은 오직 한 곳에서만 이뤄져야 한다.고참 개발자는 아무 곳에서나 어떤 작업을 하도록 코드를 쓰지 않는다. 구성 설정이 있다면 구성 설정을 읽고 쓰는 작업은 오직 한 곳에서 이뤄져야 한다. 고객의 상태를 업데이트해야 하는 경우 거기서 코드를 쓰는 것이 아니라, Customer 객체에 대한 코드를 써야 한다.
또한 애플리케이션에서 어느 부분의 동작을 변경해야 한다면 변경해야 할 곳은 하나여야 한다. 동일한 동작이 세 곳에서 일어난다면 원하는 동작을 변경하기 위해서는 세 곳 모두 변경해야 하고, 세 곳을 변경한다는 것은 잘못될 수 있는 부분이 두 군데 더 늘어난다는 의미다.
크기가 커지지 않도록 함
큰 덩어리는 나쁜 코드를 이루는 구성 요소다. 큰 클래스와 큰 메소드는 좋은 코드의 대척점에 있다. 누구나 수백, 수천(!) 줄에 이르는 거대한 클래스와 메소드를 본 적이 있을 것이다. 필자가 따르는 좋은 규칙은 접었을 때 줄 길이가 3줄을 초과하면 안 된다는 것이다.또한 전체 메소드를 편집기의 한 화면에서 볼 수 없다면 리팩터링을 해야 할 때라는 이야기도 있다.
이 경우 해야 할 가장 기본적인 일은 흔히 인버전이라고 하는 "보호 절(guard clause)"을 사용하는 것이다. 깊은 중첩이 발생하는 흔한 원인은 다수의 중첩된 if 문이다. "계속 진행할 수 있는지 묻는" 패턴으로 된 if 문을 "조건에 맞지 않으면 나가는" 패턴으로 뒤집으면(인버전) 머리를 복잡하게 하는 부울 상황을 상당수 피할 수 있다. 인버전으로 메소드의 모든 중첩이 제거되는 경우도 많다.
또 다른 전략은 항상 코드를 더 작은 메소드로 추출해서 어떤 코드 블록도 지나치게 커지지 않도록 하는 것이다. 고참 개발자는 작은 클래스와 메소드가 많이 생기는 것을 두려워하지 않는다. 물론 이러한 모든 클래스는 설명적 이름을 가져야 한다.
코드에 주석 달지 않기
이것을 마지막으로 남겨둔 이유는 많은 개발자가 발끈할 것임을 알기 때문이다. 하지만 이 부분에 대한 필자의 생각은 확고하다. 필자는 주석의 99.9%는 나쁜 코드, 나쁜 이름 또는 설명 변수의 부족을 나타내는 표시라고 굳게 믿는다. 직접 쓴 코드에 주석이 필요하다고 느낀다면 십중팔구는 코드가 명확하지 않고 이해하기 어렵기 때문이다. 코드가 명확하지 않고 이해하기 어렵다면 명확하고 이해하기 쉬워질 때까지 다시 써야 한다. (이름을 잘 짓고 설명 변수를 사용하면 도움이 된다.)또한 주석은 주석이 가리키는 코드에 물리적으로 연결되지 않으므로 다른 곳으로 표류해서 끝없는 혼란을 야기할 수 있다.
언제 코드에 주석을 다는 것이 적절할까? 경우에 따라, 예를 들어 성능상의 이유로 특이하거나 보편적이지 않은 알고리즘을 사용하도록 최적화된 코드가 종종 있다. 이와 같이 명확하지 않은 코드를 쓸 타당한 이유가 있는 경우에는 코드 주석이 필요하다.
고참 개발자가 지켜야 할 수칙은 이 밖에도 훨씬 많지만, 필자가 신참 개발자를 대상으로 멘토링한다면 여기 소개한 7가지 방법을 강조할 것이다. 경험을 통해 어렵게 얻은 지식을 기반으로 한 것이며, 실천하기도 쉽다. 안타깝지만, 대부분의 경우 고참 개발자를 뛰어난 개발자로 한 단계 업그레이드하는 것은 다름 아닌 나쁜 코드 경험이다.
editor@itworld.co.kr