1. 서론: 왜 함수는 개발자의 핵심 도구인가?
시니어 아키텍트의 관점에서 소프트웨어 개발은 복잡성을 관리하는 예술과 같습니다. 이 예술의 가장 기본적인 단위가 바로 **함수(Function)**입니다. 함수는 '특정한 작업을 위해 재활용할 수 있도록 구현한 코드 블록'이자, 프로그램의 거대한 로직을 독립적인 공간으로 나누어 관리하는 핵심 요소입니다.
잘 만들어진 함수는 마치 목공의 도구 상자와 같습니다. 망치가 필요할 때마다 매번 쇠를 녹여 새로 만들지 않듯, 개발자 역시 동일한 로직을 매번 작성하는 비효율에서 벗어나야 합니다. 함수라는 도구 상자를 잘 구축해 두면, 복잡한 시스템도 작고 관리 가능한 단위로 쪼개어 효율적으로 조립할 수 있습니다. 이것이 바로 지속 가능한 소프트웨어 아키텍처의 시작입니다.
--------------------------------------------------------------------------------
2. 코드의 중복을 없애는 마법: DRY 원칙
깨끗한 코드를 지향하는 아키텍트라면 결코 타협하지 않는 원칙이 있습니다. 바로 DRY(Don't Repeat Yourself) 원칙입니다.
DRY 원칙의 철학
앤디 헌트와 데이브 토마스는 그들의 저서 『실용주의 프로그래머』에서 다음과 같이 명시했습니다.
"모든 지식은 시스템 내에서 명확하고 권위 있는 단일한 표현을 가져야 한다."
즉, 동일한 로직이 시스템 내 여러 곳에 산재해 있다면 그것은 설계의 결함입니다.
중복 코드와 기술 부채의 위험성
중복 코드는 단순히 '복사-붙여넣기'의 산물이 아닙니다. 이는 미래의 유지보수 비용을 기하급수적으로 증가시키는 주범입니다.
 기술 부채 누적: 시간 압박으로 인해 중복된 코드를 리팩토링하지 않고 방치하면, 결국 시스템 전체의 품질이 저하되고 수정이 불가능한 상태에 빠지게 됩니다.
 버그 발생 위험: 동일한 로직이 세 군데에 있는데 두 곳만 수정했다면, 나머지 한 곳은 잠재적인 버그가 되어 시스템의 신뢰성을 무너뜨립니다.
DRY 원칙 적용 프로세스
중복을 발견했을 때 아키텍트는 다음과 같은 5단계 과정을 통해 시스템을 정화합니다.
[식별 → 추출 → 생성 → 호출 변경 → 테스트 및 검증]
1. 중복 코드 식별: 동일하거나 유사한 로직이 반복되는 위치를 찾습니다.
2. 공통 로직 추출: 중복되는 핵심 실행 코드를 분리합니다.
3. 함수/클래스 생성: 추출한 로직을 독립적이고 재사용 가능한 단위로 정의합니다.
4. 호출 방식 변경: 원본 코드의 중복 부분을 새로 만든 함수의 호출로 대체합니다.
5. 테스트 및 검증: 리팩토링 후에도 기존 기능이 의도대로 동작하는지 반드시 확인합니다.
--------------------------------------------------------------------------------
3. 작고 명확한 함수를 만드는 3가지 황금 규칙
함수를 '만드는 것'보다 중요한 것은 '잘 만드는 것'입니다. 클린 코드 에반젤리스트로서 제안하는 세 가지 규칙은 다음과 같습니다.
규칙 1: 작게, 더 작게 만들기
로버트 C. 마틴은 "함수를 만드는 첫째 규칙은 '작게!'이고, 둘째 규칙은 '더 작게!'다"라고 강조했습니다. 함수가 커질수록 그 안에 숨겨진 부수 효과(Side Effect)를 파악하기 힘들어집니다. 특히 if/else, while 문 등의 블록 내부는 가급적 단 한 줄의 함수 호출로 구성하여 추상화 수준을 높여야 합니다.
규칙 2: 한 가지만 하기 (SRP)
이는 **단일 책임 원칙(Single Responsibility Principle)**의 핵심입니다. 함수는 지정된 이름 아래에서 추상화 수준이 하나인 단계만 수행해야 합니다. 만약 함수 내에서 의미 있는 이름으로 또 다른 함수를 추출할 수 있다면, 그 함수는 이미 너무 많은 일을 하고 있는 것입니다.
규칙 3: 서술적인 이름 사용
이름이 길어지는 것을 두려워하지 마십시오. 모호한 짧은 이름보다는 길고 서술적인 이름이 주석보다 훨씬 가치 있습니다. isUserLoggedIn(), validateOrderInput()처럼 동사+명사 형태를 활용하여 기능의 의도를 명확히 표현하고, 모듈 내에서 일관된 어휘를 사용하십시오.
--------------------------------------------------------------------------------
4. 함수의 구성 요소: 매개변수와 추상화
매개변수(Parameter)와 전달인자(Argument)
아키텍처 설계와 소통의 정확성을 위해 두 용어를 명확히 구분해야 합니다.

구분정의역할위치

매개변수(Parameter)
변수(Variable)
데이터를 받기 위해 정의된 틀
함수의 정의(Definition)
전달인자(Argument)
값(Value)
함수 호출 시 실제로 전달되는 데이터
함수의 호출(Invocation)
인수 개수의 최적화
함수의 인수는 시스템의 이해를 방해하는 장애물입니다. **가장 이상적인 인수 개수는 0개(무항)**입니다. 인수가 많아질수록 테스트 케이스의 조합이 복잡해져 검증 비용이 증가합니다. 불가피한 경우를 제외하고는 3개 이상의 인수는 지양해야 하며, 필요하다면 객체로 묶어 전달하는 것을 고려하십시오.
추상화 수준의 일관성
한 함수 내에서 서로 다른 수준의 추상화가 뒤섞이면 독자는 혼란에 빠집니다. 코드는 위에서 아래로 이야기처럼 읽혀야 하며, 한 함수 내에서는 다음과 같이 계층을 분리해야 합니다.
1. 고수준 (추상적 개념): getHtml() - 전체적인 목적을 나타냄.
2. 중수준 (중간 단계): PathParser.render(pagePath) - 핵심 비즈니스 로직.
3. 저수준 (세부 구현): .append("\n") - 구체적인 데이터 조작.
--------------------------------------------------------------------------------
5. 실전 리팩토링: 나쁜 코드에서 좋은 코드로
Java 예제: OrderProcessor의 분리
하나의 거대한 함수가 모든 책임을 지고 있는 '나쁜 예'와 이를 SRP에 따라 분리한 '좋은 예'를 비교해 보십시오.
[Before: 모든 로직이 뒤섞인 큰 함수]
public class OrderProcessor {
    public void processOrder(Order order) {
        // 1. 검증 로직
        if (order == null || order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Invalid order");
        }
        // 2. 총액 계산
        double total = 0.0;
        for (Item item : order.getItems()) {
            total += item.getPrice() * item.getQuantity();
        }
        // 3. 할인 적용
        if (total > 100) { total *= 0.9; }
        // 4. 결제 처리 및 이메일 전송 (생략)
        System.out.println("Processing payment of " + total);
    }
}
[After: 작고 명확한 함수로 분리된 구조]
public class OrderProcessor {
    public void processOrder(Order order) {
        validateOrder(order);
        double total = calculateTotal(order);
        double finalAmount = applyDiscount(total);
        processPayment(finalAmount);
        sendConfirmation(order.getCustomerEmail());
    }

    private void validateOrder(Order order) { /* 검증 세부 로직 */ }
    private double calculateTotal(Order order) { /* 계산 세부 로직 */ }
    private double applyDiscount(double total) { /* 할인 세부 로직 */ }
    // ... 나머지 세부 함수들
}
Python 예제: 데코레이터를 활용한 DRY 실천
보일러플레이트(반복되는 코드)인 시간 측정 로직을 데코레이터로 분리하여 비즈니스 로직의 순수성을 지킵니다.
[Before: 시간 측정 로직이 매번 반복됨]
def process_user_data(self, data):
    start_time = time.time() # 중복 발생
    # 비즈니스 로직...
    print(f"완료: {time.time() - start_time}")
[After: @timing_logger로 공통 로직 추출]
@timing_logger
def process_user_data(self, data):
    return [item.upper() for item in data]
리팩토링 전후 비교

지표리팩토링 전 (Monolithic)리팩토링 후 (Clean Code)

가독성
세부 로직에 파묻혀 전체 흐름 파악 불가
함수 이름만으로 비즈니스 의도 파악 가능
변경 용이성
작은 수정에도 전체 함수를 검토해야 함
특정 기능을 수행하는 작은 함수만 수정
버그 수정 효율
문제 지점 탐색이 어렵고 수정 범위가 넓음
독립된 단위에서 버그를 빠르게 격리 가능
테스트 가능성
입력 조합이 너무 많아 테스트가 불가능함
각 작은 함수별로 명확한 단위 테스트 가능
--------------------------------------------------------------------------------
6. 함수를 완성하는 마지막 단계: 단위 테스트(Unit Testing)
아무리 깨끗한 함수라도 검증되지 않았다면 신뢰할 수 없습니다. 단위 테스트의 6가지 원칙을 준수하십시오.
1. Fast (신속): 테스트는 밀리초 단위로 빠르게 실행되어야 합니다.
2. Isolated (고립): DB나 API 등 외부 의존성 없이 독립적으로 실행되어야 합니다.
3. Repeatable (반복 가능): 어떤 환경에서도 동일한 결과를 보장해야 합니다.
4. Reliable (신뢰성): 테스트 코드는 운영 코드와 동일한 수준의 품질로 관리되어야 합니다.
5. Readable (가독성): 테스트 그 자체로 함수의 명세서 역할을 해야 합니다.
6. Self-checking (자가 진단): 결과 성공 여부가 자동으로 판별되어야 합니다.
AAA/GWT 구조를 통한 검증
C# 예시를 통해 예측 가능한 테스트 구조인 **Arrange(Given) - Act(When) - Assert(Then)**를 적용해 보겠습니다.
[Test]
public void TestAddIntegers()
{
    // Given (Arrange): 테스트 환경 설정
    int a = 1;
    int b = 2;

    // When (Act): 실제 함수 실행
    int result = AdditionExample.Add(a, b);

    // Then (Assert): 결과가 의도와 맞는지 검증
    Assert.AreEqual(3, result);
}
--------------------------------------------------------------------------------
7. 결론: 지속 가능한 개발을 위한 첫걸음
함수를 만드는 행위는 단순히 기계적인 코딩이 아니라, 소프트웨어의 수명을 결정하는 아키텍처 설계의 최소 단위입니다. '중복 제거(DRY)', '작은 크기', '단일 책임(SRP)'이라는 원칙을 지키는 습관은 여러분을 단순한 코더에서 숙련된 엔지니어로 성장시킬 것입니다.

+ Recent posts