SW 개발 일반

SW 개발의 기본: 추상화

growdai1y 2025. 2. 22. 01:09

소프트웨어 개발에서 추상화(Abstraction)는 핵심적인 개념입니다.

 

AI가 생성한 추상화!!
AI가 생성한 추상화!!

 

하지만 "추상화를 잘해야 한다"는 조언만으로는 실제 코드에서 어떻게 적용해야 할지 감이 오지 않는 경우가 많습니다. 오랜 시간 개발을 하며 느낀 점은, 추상화는 단순히 코드를 깔끔하게 만드는 것이 아니라 SW의 정체성을 유지할 수 있는 이름을 부여하는 것입니다. 다만 이런 설명도 어렵죠. 

 

그렇다면, 어떻게 하면 좋은 추상화를 할 수 있을까요? 그리고 반대로, 불필요한 추상화를 피하려면 어떻게 해야 할까요?


추상화를 잘하라는 말이 모호한 이유

처음에는 "추상화를 잘해야 한다"는 말을 자주 했습니다. 하지만 개발자마다 이해하는 방식이 달라 결과물이 천차만별이었습니다.

  • 어떤 개발자는 너무 낮은 수준에서 추상화를 적용했습니다.
    • 예를 들어, 외부 라이브러리를 그대로 사용하는데도 불필요한 감싸기를 하지 않았습니다.
    • 결과적으로 특정 라이브러리에 종속적인 코드가 늘어나 유지보수가 어려워졌습니다.
  • 또 다른 개발자는 과한 추상화를 적용했습니다.
    • 단순한 String.format() 같은 기본 함수조차 인터페이스로 감쌌습니다.
    • 결국, 불필요한 코드 계층이 생기면서 오히려 유지보수가 어려워졌습니다.

이러한 문제를 반복해서 경험한 뒤, "추상화를 잘하라"는 말을 더 이상 하지 않기로 했습니다. 대신, 더 명확한 기준을 제시하는 방식으로 피드백을 바꾸었습니다.


추상화를 제대로 적용하는 방법

이제 저는 추상화에 대한 피드백을 다음과 같이 주고 있습니다.

(1) 외부에서 제공하는 것은 원하는 이름으로 가려라

개발자가 직접 작성한 코드가 아니라, 외부 라이브러리나 API를 사용해야 한다면 바로 호출하지 말고 인터페이스를 만들어 한 번 감싸는 것이 좋습니다.

적용 사례 (좋은 추상화)

외부 날씨 API를 사용하는 코드가 있다고 가정하겠습니다.

(잘못된 예시 - 직접 호출하는 방식)

class WeatherApp {
    public String getWeather() {
        OpenWeatherMapClient client = new OpenWeatherMapClient();
        return client.fetchWeatherData();
    }
}

이렇게 하면 외부 API에 직접 종속되기 때문에, API 변경이 생기면 모든 호출 코드를 수정해야 합니다.

(좋은 예시 - 인터페이스를 통한 추상화)

interface WeatherService {
    String getWeather();
}

class OpenWeatherMapService implements WeatherService {
    private OpenWeatherMapClient client = new OpenWeatherMapClient();

    public String getWeather() {
        return client.fetchWeatherData();
    }
}

이렇게 하면 WeatherService라는 인터페이스를 통해 외부 API와의 의존성을 관리할 수 있습니다.

  • API 변경이 발생해도 OpenWeatherMapService만 수정하면 됩니다.
  • 테스트 시 MockWeatherService를 만들어 독립적인 단위 테스트가 가능합니다.

이처럼 외부 의존성을 추상화하는 것은 반드시 필요합니다.


(2) 프리미티브 기반 라이브러리라면 굳이 감싸지 말아라

반면, 언어에서 기본적으로 제공하는 기능을 단순히 감싸는 것은 의미가 없습니다.

잘못된 사례 (불필요한 추상화)

interface StringFormatter {
    String format(String input);
}

class DefaultStringFormatter implements StringFormatter {
    public String format(String input) {
        return String.format("Formatted: %s", input);
    }
}

 

이렇게 하면 인터페이스를 만들긴 했지만, 결국 내부적으로 String.format()을 그대로 호출할 뿐입니다.

더 나은 접근 방법은, 이 경우에는 그냥 String.format()을 직접 사용하는 것입니다.


추상화를 적용하는 기준

이제는 팀원들에게 추상화를 적용할지 말지를 판단하는 기준을 명확히 설명하고 있습니다.

꼭 추상화해야 하는 경우 굳이 추상화하지 않아도 되는 경우
외부 API, 라이브러리를 사용할 때 언어가 기본 제공하는 기능을 그대로 사용할 때
API 변경 가능성이 높을 때 별다른 장치 없이도 단위 테스트가 가능한 경우
유지보수성과 테스트 용이성이 증가할 때 감싸는 것 자체가 목적이 되는 경우

 

이 기준을 적용한 후, 팀원들이 불필요한 추상화는 하지 않으면서도 꼭 필요한 부분은 추상화하는 방식으로 개발을 진행하게 되었습니다.


결론

추상화는 "잘해야 한다"는 말만으로는 해결되지 않습니다. 언제 추상화를 적용하고, 언제 하지 않아도 되는지를 명확하게 판단할 수 있는 기준이 필요합니다.

  • 외부 의존성은 반드시 인터페이스로 감싸 제어권을 확보해야 합니다.
  • 하지만, 언어가 제공하는 프리미티브 기반 라이브러리라면 굳이 감싸지 않아도 됩니다.
  • 추상화의 목적은 "깔끔한 코드"가 아니라, 제어권 확보와 유지보수성 향상이어야 합니다.

이런 기준을 바탕으로 개발을 하다 보면, 자연스럽게 "진짜 필요한 추상화"를 적용할 수 있게 됩니다. 다만 이런 수준에 도달하기 위해서는 개발자 본인이 체감할 수 있는 추상화의 정도를 연습하고 알아 갈 수 있게 쉬운 피드백이 필요합니다.

 

 

반응형