하위 호환성을 가지는 소프트웨어

ilotoki·2026년 3월 16일

하위호환성은 중요하다. 소프트웨어의 사용자들이 지금 짠 코드가 1년 뒤, 5년 뒤, 10년 뒤에도 그대로 원하던 대로 작동하는 것을 보장하면 코드를 더욱 자신감 있게 짜고 버전을 올리는 것이 덜 두려워질 것이다.

우선 파이썬의 경우부터 살펴보면 하위호환성보단 언어의 완전성을 더욱 추구한다. 파이썬 언어와 라이브러리 개발자들은 특정한 기능이 사용되어선 안 된다고 생각하거나 거의 완전한 상위 호환성의 기능이 있을 때 기존의 기능을 deprecate하거나 제거하는 데에 크게 주저하지 않는다.
아마 이러한 파이썬 개발자들의 관점을 적나라하게 보여주는 예시가 파이썬 2에서 3으로의 전환일 것이다. 파이썬 개발자들은 파이썬 3으로 가면서 파이썬 2에서 나쁘다고 생각했던 엄청나게 많은 기능들을 하위 호환적이지 않는 방식으로 변경했다.
물론 파이썬 3으로의 전환은 상당히 고통스러웠고, 더 이상 이러한 방식의 전환은 없을 것이라는 이야기가 많지만, 한 번에 대량의 하위 호환적이지 않는 변경을 하지 않겠다고 이야기할 뿐이지 여러 파이썬 버전에 걸쳐 다양한 하위 호환적이지 않은 변경을 하는 것은 여전하다.

파이썬 언어와 라이브러리 개발자들의 이러한 태도는 두 가지에서 기인하는 것 같다. 파이썬으로 개발하는 사람들은 간결성, 사용하기 편한 것을 무엇보다도 우선한다. 아름답고 간결한 코드를 짜는 것에 대한 열망이 다른 언어의 개발자들에 비해 강하다. 따라서 하위 호환성을 유지시키는 대신 나쁘고, 못생긴 코드를 계속해서 유지관리하기보단 아예 제거해버리고 하위 호환성을 깨뜨리지만 더 나은 코드를 짜도록 강제하는 방식을 더 선호한다.

파이썬 자체의 언어적 한계로 한몫한다. 파이썬에서는 어떤 API의 공개 정도를 제한하기 매우 어렵다.
파이썬은 퍼블릭 인터페이스와 비공개 인터페이스를 구분할 때 거의 컨벤션에 의존한다. 언더스코어로 시작하는 이름이 있다면 비공개 인터페이스로 취급하고, 혹은 __all__에 포함되어 있는지와 같은 정보를 이용해 인터페이스의 공개 정도를 구분한다. 그러나 사용자는 엄청나게 쉽게 인터페이스에 대한 컨벤션을 무시하고 비공개 인터페이스에 접근할 수 있다.
네임 멩글링이라는 기능을 통해 비공개 인터페이스에 접근하기 조금 더 귀찮게 만들 수는 있지만 애초에 비공개 인터페이스를 만들 때 사용하라고 만들어진 도구도 아니고, 마찬가지로 조금만 코드를 바꾸면 손쉽게 접근할 수 있다.
이는 파이썬의 인터프러터적인 속성 상이 한계에 의하기도 하지만, 실제로 파이썬을 사용하거나 개발하는 사람들은 이것이 기능의 일부라고 생각한다. 물론 비공개 인터페이스를 접근하는 것이 바람직한 일은 아니지만, 만약 라이브러리 제작자가 생각했던 방향과는 무관하게 자신의 필요에 따라 라이브러리를 트윅하기 쉬워진다는 장점이 있다. 그렇기 때문에 라이브러리 제작자는 어떤 걸 퍼블릭 인터페이스로 설정하고 어떤 걸 하지 않을지에 대해 너무 깊게 고민하지 않고 일단 적당히 작성해놓은 뒤 사용자가 필요한 경우 비공개 인터페이스더라도 갖다 사용하도록 만들 수 있고, 사용자 입장에선 제대로 만들어지지 않았거나 내 필요와 다른게 인터페이스가 설정된 경우에도 인터페이스에서 설정한 제한을 넘어서 자신이 원하는 값을 편하게 설정할 수 있다는 장점이 있다.

공개와 비공개 인터페이스 간의 차이가 매우 흐릿하기 때문에, 비공개 인터페이스를 바꾸는 것 또한 상황에 따라 꽤 많은 사용자들의 코드를 망가뜨릴 수 있다. 결국 공개 인터페이스가 변경되던, 비공개 인터페이스가 변경되건 상관없이 많은 사용자들의 코드를 망가뜨릴 수 있는 위험에 처해 있다는 것이다. 이렇게 되면 개발자들이 취할 수 있는 액션은 보통 둘 중 하나이다. 비공개 인터페이스의 하위 호환성까지 생각해서 코드가 망가지지 않도록 매우 매우 주의하면서 거의 어떠한 변화도 만들지 않거나, 그냥 관련된 문제를 덜 고려하는 대신 원하는 변화를 빠르게 만들어나가는 것이다. 보통의 파이썬 개발자들은 후자를 택한다. 물론 일반적인 경향성이 그렇다는 것이 공개 인터페이스와 비공개 인터페이스를 엄격하게 구분하고 하위 호환성을 잘 지켜서 작성되는 라이브러리도 많다. 그렇지만 그런 라이브러리들조차도 비공개 인터페이스만을 변경했을 때도 일부 사용자의 코드를 망가뜨릴 수 있다는 문제로부터 자유롭지는 못한다.

파이썬의 경우는 그렇다고 하지만, 다른 언어의 경우는 어떨까? C나 C++은 아마도 하위 호환성을 걱정하는 걸로는 정말 탑을 달릴 것이다.
C나 C++의 경우 하위 호환성을 매우 매우 따진다. 사실상 두 언어에서 하위 호환성을 깨뜨리는 기능은 거의 추가되기 어렵다고 봐야 할 것이다. 물론 이 두 언어도 특정 기능을 deprecate시키거나 제거하는 경우가 없지는 않지만, 거의 대부분의 경우 하위 호환성을 지키기 위해 더 좋은 기능이 있더라도 더 나쁜 기능을 그대로 유지시키는 경우가 많다.

러스트는 다른 언어들과는 다른 정말 독특한 방식을 택한다. 러스트도 C나 C++과 비슷하게 하위 호환성을 매우 중시한다. 2015년에 나온 러스트 1.0 이후로 러스트의 코드는 항상 호환된다. 그렇다면 러스트도 C나 C++처럼 하위 호환성이 없는 언어적 향상은 기대할 수 없는 것일까? 러스트는 어떻게 발전할 수 있을까?
이 질문에 대해 러스트는 두 가지로 답한다. 첫 번째는 nightly이다. 러스트는 특정한 기능이 완벽히 안정적인 API에 추가될 수 있다고 생각할 때까지 새 기능들을 나이틀리 러스트에서 특정 feature를 키는 경우에만 사용할 수 있도록 만든다. 이 기능들은 다시 없어지거나, 하위 호환성 없도록 변경되거나, 추가될 수 있다. 이렇게 하면, 특정 기능을 사용할 방법을 아예 막지 않으면서도, 언어에 하위 호환성을 깨뜨리는 변화를 만드는 것을 방지할 수 있다.
두 번째는 에디션이라는 정말 정말 놀랍고 특이한 시스템이다. 에디션은 러스트에서 하위 호환성이 없는 문법적인 변화를 포함시키는 방법론이다. 방금은 러스트는 1.0부터 호환된다면서, 갑자기 이제 와서 하위 호환성이 없는 문법적 변화가 있다고 말하는 걸까?
특정 에디션을 사용하면 그 전 버전에 없었더라도 그 에디션에 맞는 문법과 키워드를 사용할 수 있다. 예를 들어 러스트 2018 에디션에서는 dyn, async, await, try와 같은 새로운 키워드가 추가되었다. 그런데 그러면 옛날에 이 키워드들을 사용했던 코드들은 어떻게 되는 걸까?
러스트 에디션의 창의적인 발상은 러스트의 버전에디션이 구분된다는 것이다. 예를 들어, 러스트의 현재 최신 에디션은 2024 에디션인데, 최신 버전에서 에디션을 2024로 설정할 수도 있지만, 방금 설명한 2018 에디션은 물론이고 심지어는 2015 에디션으로 설정할 수도 있다. 즉, 내가 러스트 1.0에서 짠 코드는 최신 버전에서도 2015 에디션으로 설정하기만 하면 손쉽게 하위 호환성 있도록 문제없이 컴파일할 수 있다.
예를 들어 위에서 새로운 키워드들이 추가되었다고 했었는데, 러스트 에디션을 2015로 설정하면 최신 버전에서도 async라는 이름의 변수를 문제 없이 만들고 사용할 수 있다!
러스트의 하위 호환성에 대한 약속이 의미가 있기 위해선 에디션을 내가 원하는 범위에 대해서 사용해야 한다. 러스트 2024 에디션으로 짠 코드들이 2021 에디션으로 짠 코드들과 같이 사용될 수 없고 끼리끼리만 컴파일된다면 사실상 하위 호환성이 없는 새로운 버전이 생긴 것과 크게 다름이 없을 것이다. 그러나 러스트에서 에디션은 크레이트 단위로 설정할 수 있고, 크레이트의 에디션과는 상관없이 상호적으로 사용 가능하다. 정말 흥미롭고 유용한 디자인이 아닐 수가 없다!

0개의 댓글