[태그:] 배드 코드

  • “코드는 썩는다”… 당신의 코드는 안녕하신가요? 클린 코드 vs 배드 코드

    “코드는 썩는다”… 당신의 코드는 안녕하신가요? 클린 코드 vs 배드 코드

    소프트웨어 개발자에게 코드는 단순한 명령어의 나열이 아닌, 자신의 생각과 논리를 표현하는 ‘글’입니다. 그리고 모든 글이 그렇듯, 코드에도 잘 쓴 글과 못 쓴 글이 있습니다. 우리는 잘 쓴 코드를 ‘클린 코드(Clean Code)’라 부르고, 못 쓴 코드를 ‘배드 코드(Bad Code)’ 또는 ‘코드 스멜(Code Smell)’이 풍기는 코드라고 부릅니다. 배드 코드는 당장은 잘 동작하는 것처럼 보일지 모릅니다. 하지만 시간이 지나면서 그 코드는 서서히 ‘썩기’ 시작합니다. 작은 기능을 하나 추가하는 데 며칠 밤을 새워야 하고, 버그를 하나 고치면 예상치 못한 다른 곳에서 새로운 버그가 터져 나오는 ‘유지보수의 지옥’을 선사합니다.

    전설적인 프로그래머 마틴 파울러는 “어떤 바보라도 컴퓨터가 이해하는 코드를 짤 수 있다. 하지만 좋은 프로그래머는 ‘사람’이 이해하는 코드를 짠다”고 말했습니다. 클린 코드는 바로 이 철학의 정수입니다. 나 자신을 포함한 미래의 동료들이 쉽게 읽고, 이해하고, 수정할 수 있도록 작성된 코드. 이는 단순히 개인의 코딩 스타일 문제가 아니라, 프로젝트의 생산성과 안정성, 그리고 팀의 협업 효율을 결정짓는 가장 중요한 ‘프로의 덕목’입니다.

    본 글에서는 우리를 좌절시키는 배드 코드의 전형적인 특징들은 무엇이며, 이를 어떻게 개선하여 빛나는 클린 코드로 탈바꿈시킬 수 있는지 그 원칙과 실천 방법을 구체적인 코드 예시를 통해 명확하게 비교 분석해 보겠습니다. 이 글을 통해 여러분은 자신의 코드를 한 단계 더 높은 수준으로 끌어올리는 구체적인 통찰을 얻게 될 것입니다.


    배드 코드 (Bad Code): 기술 부채를 쌓는 악마의 속삭임

    배드 코드는 단기적으로는 빠르게 기능을 구현한 것처럼 보이지만, 장기적으로는 프로젝트 전체를 병들게 하는 ‘기술 부채(Technical Debt)’를 쌓습니다. 지금 당장 이자를 내지 않아도 되는 카드빚처럼, 언젠가는 엄청난 시간과 노력이라는 이자를 붙여 되돌려받게 됩니다.

    배드 코드의 전형적인 특징과 증상

    1. 의미를 알 수 없는 이름 (Mysterious Names)

    변수, 함수, 클래스의 이름이 그 역할과 의도를 전혀 설명하지 못하는 경우입니다. 코드를 읽는 사람은 이것이 도대체 무엇을 하는 코드인지 추측하기 위해 다른 부분을 모두 뜯어봐야 합니다.

    Bad Code:

    Java

    public List<int[]> getThem(List<int[]> list1) {
    List<int[]> list2 = new ArrayList<int[]>();
    for (int[] x : list1) {
    if (x[0] == 4) {
    list2.add(x);
    }
    }
    return list2;
    }
    • getThemlist1list2xx[0] == 4? 이 코드는 암호 해독에 가깝습니다. ‘4’가 무엇을 의미하는지 마법의 숫자(Magic Number)일 뿐입니다.

    2. 거대한 함수 (Long Function)

    하나의 함수가 수백, 수천 줄에 달하며 너무 많은 일을 한꺼번에 처리하려고 하는 경우입니다. 이런 함수는 이해하기 어려울 뿐만 아니라, 작은 수정도 매우 어렵게 만듭니다. ‘단일 책임 원칙(Single Responsibility Principle)’을 명백히 위반하는 사례입니다.

    Bad Code:

    Java

    public void processOrder() {
    // 1. 주문 데이터 유효성 검사
    // ... (수십 줄의 코드)

    // 2. 재고 확인 및 차감
    // ... (수십 줄의 코드)

    // 3. 결제 처리
    // ... (수십 줄의 코드)

    // 4. 배송 정보 생성
    // ... (수십 줄의 코드)

    // 5. 고객에게 이메일 발송
    // ... (수십 줄의 코드)
    }
    • ‘주문 처리’라는 거대한 작업 안에 너무 많은 책임이 뒤섞여 있습니다. 만약 이메일 발송 로직만 바꾸고 싶어도, 전체 함수의 맥락을 모두 이해해야 하는 부담이 생깁니다.

    3. 깊은 중첩과 많은 들여쓰기 (Deeply Nested Logic)

    ifforwhile 문 등이 여러 겹으로 깊게 중첩되어 코드의 흐름을 파악하기 매우 어려운 경우입니다. 코드가 오른쪽으로 계속해서 밀려나는 ‘화살표 모양 코드(Arrowhead Code)’는 배드 코드의 대표적인 신호입니다.

    Bad Code:

    Java

    public void process(User user) {
    if (user != null) {
    if (user.isActivated()) {
    if (user.hasPermission("ADMIN")) {
    // 실제 로직
    } else {
    log.error("권한 없음");
    }
    } else {
    log.error("비활성 사용자");
    }
    } else {
    log.error("사용자 없음");
    }
    }
    • 실제 핵심 로직에 도달하기 위해 세 번의 if 문을 거쳐야 합니다. 이런 구조는 버그를 유발하기 쉽고 테스트하기 어렵습니다.

    4. 불필요한 주석 (Useless Comments)

    코드는 그 자체로 의도를 설명해야 합니다. 코드만 봐도 알 수 있는 내용을 중복해서 설명하거나, 지금은 사용하지 않는 과거의 코드를 주석 처리한 채 방치해 두는 것은 코드에 소음만 더할 뿐입니다. 좋은 코드는 주석이 거의 필요 없는 코드입니다.

    Bad Code:

    Java

    // i를 1 증가시킴
    i++;

    // 고객 클래스
    public class Customer { ... }

    // 2023-10-26: 임시로 막아둠. 나중에 다시 살려야 함.
    // processOldLogic();
    • 이런 주석들은 아무런 가치를 제공하지 못하며, 오히려 코드가 변경될 때 함께 관리되지 않아 거짓 정보를 제공할 위험만 높입니다.

    클린 코드 (Clean Code): 미래의 나를 위한 배려

    클린 코드는 로버트 C. 마틴(Uncle Bob)이 그의 저서 “Clean Code”에서 체계적으로 정리하며 널리 알려졌습니다. 클린 코드는 단순함, 가독성, 그리고 유지보수 용이성에 초점을 맞춘 코드 작성 철학이자 실천 방법론입니다.

    클린 코드 작성을 위한 핵심 원칙

    1. 의미 있는 이름 짓기 (Meaningful Names)

    변수, 함수, 클래스의 이름은 그것이 ‘무엇’이고, ‘왜’ 존재하며, ‘어떻게’ 사용되는지에 대한 정보를 담아야 합니다. 이름만 보고도 그 역할과 의도를 명확히 파악할 수 있어야 합니다.

    Clean Code (Refactored from Bad Code):

    Java

    // Bad Code의 getThem 함수 개선
    final int FLAGGED = 4;
    final int STATUS_VALUE_INDEX = 0;

    public List<int[]> getFlaggedCells(List<int[]> gameBoard) {
    List<int[]> flaggedCells = new ArrayList<int[]>();
    for (int[] cell : gameBoard) {
    if (cell[STATUS_VALUE_INDEX] == FLAGGED) {
    flaggedCells.add(cell);
    }
    }
    return flaggedCells;
    }
    • getThem은 getFlaggedCells로, list1은 gameBoard로, list2는 flaggedCells로 바뀌면서 코드의 의도가 명확해졌습니다. 마법의 숫자 4는 FLAGGED라는 의미 있는 상수로 대체되었습니다.

    2. 함수는 작게, 그리고 한 가지 일만 (Small Functions, Do One Thing)

    함수는 가능한 한 작아야 하고, 추상화 수준이 하나여야 하며, 오직 ‘한 가지 일’만 책임져야 합니다. 이렇게 잘게 쪼개진 함수들은 재사용하기 쉽고 테스트하기 용이하며, 전체적인 코드의 가독성을 높여줍니다.

    Clean Code (Refactored from Bad Code):

    Java

    // Bad Code의 processOrder 함수 개선
    public void processOrder(Order order) {
    validateOrder(order);
    processPayment(order);
    updateInventory(order);
    createShippingInfo(order);
    sendConfirmationEmail(order);
    }

    private void validateOrder(Order order) { /* ... */ }
    private void processPayment(Order order) { /* ... */ }
    private void updateInventory(Order order) { /* ... */ }
    private void createShippingInfo(Order order) { /* ... */ }
    private void sendConfirmationEmail(Order order) { /* ... */ }
    • processOrder 함수는 이제 전체적인 작업의 흐름을 보여주는 ‘목차’ 역할을 합니다. 각 세부 작업은 자신의 이름으로 책임이 명확히 분리된 작은 함수들로 위임되었습니다. 이제 이메일 발송 로직을 수정하고 싶다면 sendConfirmationEmail 함수만 보면 됩니다.

    3. 중첩 줄이기 (Reduce Nesting)

    깊게 중첩된 로직은 ‘빠르게 실패하기(Fail Fast)’ 또는 ‘가드 클로즈(Guard Clauses)’ 패턴을 사용하여 평탄하게 만들 수 있습니다. 함수의 시작 부분에서 예외적인 상황이나 에러 케이스를 먼저 처리하고 즉시 반환(return)하면, 주된 로직은 들여쓰기 없이 깔끔하게 유지될 수 있습니다.

    Clean Code (Refactored from Bad Code):

    Java

    // Bad Code의 process 함수 개선
    public void process(User user) {
    if (user == null) {
    log.error("사용자 없음");
    return;
    }
    if (!user.isActivated()) {
    log.error("비활성 사용자");
    return;
    }
    if (!user.hasPermission("ADMIN")) {
    log.error("권한 없음");
    return;
    }

    // 실제 로직 (들여쓰기 없음)
    }
    • if-else의 중첩 구조가 사라지고, 예외 조건을 위에서부터 차례대로 검사하고 빠져나가는 훨씬 더 읽기 편한 코드가 되었습니다.

    4. 주석 대신 코드로 설명하라 (Explain Yourself in Code)

    정말로 필요한 주석(법적인 고지, 복잡한 알고리즘에 대한 설명 등)도 있지만, 대부분의 주석은 코드를 더 명확하게 만들려는 노력의 실패를 의미합니다. 주석을 달고 싶다는 생각이 들면, 먼저 코드를 리팩토링하여 그 의도를 더 잘 드러낼 수 없는지 고민해야 합니다.

    Clean Code (Refactored from Bad Code):

    Java

    // Bad: 주석에 의존하는 코드
    // 직원의 월급이 5000을 초과하고, 근무 기간이 60개월 이상인지 확인
    if (employee.salary > 5000 && employee.monthsOfService > 60) {
    ...
    }

    // Good: 코드가 스스로를 설명함
    if (employee.isEligibleForFullBenefits()) {
    ...
    }

    // Employee 클래스 내부에...
    public boolean isEligibleForFullBenefits() {
    boolean isOverSalaryThreshold = salary > 5000;
    boolean hasSufficientService = monthsOfService > 60;
    return isOverSalaryThreshold && hasSufficientService;
    }
    • 복잡한 조건문을 의미 있는 이름을 가진 함수로 추출(Extract Method)함으로써, 주석 없이도 코드의 의도를 명확하게 전달할 수 있습니다.

    클린 코드의 비즈니스 가치: 왜 우리는 노력해야 하는가?

    클린 코드를 작성하는 것은 단순히 개발자의 미적 만족감을 위한 것이 아닙니다. 이는 프로젝트와 회사의 성공에 직접적인 영향을 미치는 중요한 경제 활동입니다.

    • 개발 속도 향상: 깨끗한 코드는 이해하기 쉽기 때문에 새로운 기능을 추가하거나 기존 기능을 변경하는 속도가 훨씬 빠릅니다. 배드 코드는 당장은 빠를지 몰라도, 시간이 지날수록 부채가 쌓여 개발 속도를 극적으로 저하시킵니다.
    • 유지보수 비용 감소: 소프트웨어 개발 비용의 상당 부분은 초기 개발이 아닌 유지보수 단계에서 발생합니다. 클린 코드는 버그 발생 가능성을 낮추고, 버그가 발생하더라도 원인을 찾고 수정하기 쉬워 전체적인 유지보수 비용을 크게 줄여줍니다.
    • 팀 생산성 증대: 코드는 혼자 쓰는 일기가 아닙니다. 여러 개발자가 함께 읽고 수정하는 공동의 자산입니다. 모두가 이해할 수 있는 깨끗한 코드는 팀원 간의 원활한 협업을 가능하게 하고, 신규 멤버가 프로젝트에 적응하는 시간도 단축시킵니다.

    위 그래프는 프로젝트 초반에는 배드 코드가 더 빠른 생산성을 보이는 것처럼 보이지만, 시간이 지남에 따라 기술 부채가 쌓여 생산성이 급격히 떨어지는 반면, 클린 코드는 꾸준히 높은 생산성을 유지함을 보여줍니다.


    마무리: 클린 코드는 습관이자 전문가의 책임이다

    클린 코드는 한 번에 도달할 수 있는 목표가 아니라, 더 나은 코드를 작성하기 위해 끊임없이 노력하고 개선해 나가는 ‘과정’이자 ‘습관’입니다. 보이스카우트 규칙인 “언제나 처음 왔을 때보다 깨끗하게 해놓고 캠프장을 떠나라”는 말을 기억해야 합니다. 내가 작성하는 코드는 물론, 동료의 코드를 수정할 때도 조금이라도 더 깨끗하게 만들려는 노력이 쌓여 프로젝트 전체의 건강함을 만듭니다.

    배드 코드는 빠른 길처럼 보이지만 결국은 프로젝트를 실패로 이끄는 가장 느린 길입니다. 반면, 클린 코드를 작성하는 것은 당장은 조금 더 고민하고 노력해야 하는 길처럼 보이지만, 장기적으로는 우리 모두를 성공으로 이끄는 가장 빠르고 현명한 길입니다. 코드를 작성하는 모든 개발자는 자신의 결과물이 가져올 장기적인 영향에 대해 책임감을 가져야 하며, 클린 코드는 그 책임감을 실천하는 가장 확실한 방법입니다.