
지금까지 정글에서 진행했던 프로젝트는 알고리즘 문제풀이나 간단한 자료구조 구현 등 비교적 짧은 과제들이었다. 그래서 코드가 조금 복잡하고 읽기 어려웠어도 돌아가기만 하면 큰 문제가 없었다.
하지만 이번 주에는 레드블랙 트리를 구현하는 데 한 주를 다 썼다. 평소보다 구현해야 할 내용도 많았고, 그만큼 코드도 많이 길어졌다.
모든 테스트 케이스를 통과한 후 내 코드를 다시 봤는데, 너무 스파게티 코드여서 내가 직접 작성했는데도 이해가 잘 안 됐다. 기억을 되새기는 과정에서 코드를 깔끔하게 다듬는 리팩토링 작업을 진행했다.
우선 반복되는 동일한 코드를 함수로 분리하는 작업부터 진행했다. 레드블랙 트리의 삽입, 삭제 과정에서는 두 노드의 색을 바꾸는 경우가 꽤나 많다. 내 원래 코드를 확인하니, 두 노드의 색을 바꾸는 코드가 무려 6번이나 반복되고 있었다.
하지만 결국 하는 일은 두 노드를 골라서 색을 바꾸는 것이므로, 함수로 처리할 수 있을 것 같았다. 두 노드의 주소를 포인터 매개변수로 받아 색을 바꾸는 swap_color 함수를 만들었다.
그 결과 중복되는 코드를 크게 줄일 수 있다. 만약 이 부분에서 에러가 발생하더라도 일일이 모든 경우를 체크할 필요 없이, 함수에 문제가 있는지만 체크하면 되므로 유지보수 측면에서도 훨씬 효율적일 것이다.
두 자식이 있는 노드를 삭제할 때는, 왼쪽 서브트리 중 최댓값을 가진 노드인 계승자 노드가 삭제되는 노드를 대신한다. 맨 처음에는 단순히 계승자 노드를 찾은 뒤, 삭제할 위치의 노드에 계승자 노드의 값을 복사하고, 계승자 노드를 삭제했다.
다만 본 예제에는 노드에 숫자 값만을 저장했지만, 실제 RB 트리에는 문자열 등 보다 다양한 데이터를 저장할 수 있다. 노드에 저장된 데이터량이 많을수록 이를 일일이 복사하는 것은 비효율적이라고 판단했다.
따라서 값을 복사하지 않고, 노드의 left, right, parent 포인터를 조정하여 노드 간 연결관계를 조정하는 방식으로 코드를 개선하였다.
값의 복사 없이, 노드 간 연결관계만을 바꿈으로써 계승자 노드를 삭제 노드 위치로 이동시킬 수 있다. 노드에 저장된 데이터량이 많을수록, 보다 효율적인 방법이 되지 않을까 생각된다.

레드블랙 트리에서 삽입, 삭제를 구현할 때는 부모, 삼촌, 형제, 조부모 등 인접 노드의 색을 확인하고 바꾸거나, 해당 노드를 기준으로 회전할 일이 많다.
이러한 노드들의 주소를 미리 포인터에 저장해 두면, 추후 코드를 작성하는 데 큰 도움이 된다.
맨 처음에는 이렇게 변수를 두지 않고 일일이 newNode -> parent -> parent -> right 식으로 직접 삼촌을 찾았다.
하지만 코드를 읽고 관리하는 입장에서는, 이해하기 쉬운 이름의 변수를 만들어 관리하는 것이 훨씬 편리한 것 같다. 꼭 코드 길이를 줄이는 것뿐만 아니라, 읽고 이해하기 쉬운 코드를 만드는 것도 리팩토링이 아닐까 싶다.

말이 좀 거칠지만, 이 말은 무려 어니스트 헤밍웨이가 한 말이다.
문과생인 나는 대학 시절 리포트를 쓸 일이 많았는데, 처음 완성한 초고는 늘 오타 투성이에 앞뒤도 안 맞는 문장으로 가득했다. 결국 무조건 고쳐야 했다. 초고는 완성본이 될 수 없었다.
프로그래밍도 마찬가지인 것 같다. 처음엔 어떻게든 돌아가는 코드를 짜는 게 목표다. 하지만 결국에는 유지보수하기 쉽고, 협업 과정에서 남들이 읽고 이해하기 쉬운 코드를 만들기 위해서는 리팩토링은 필수인 것 같다.
그렇다고 해서 리팩토링을 부담스러워하지 말자. 고칠 기회가 있다는 건 처음부터 완벽한 코드를 짜려고 애쓰지 않아도 된다는 소리다. 어차피 초고는 쓰레기일 테니까. 그다음에 리팩토링으로 고치면 되는 거다.
모든 초고는 쓰레기지만, 눈물나는 리팩토링의 결과물은 원석이 될 것이다.
대본
저는 레드블랙 트리를 한 차례 구현하고 리팩토링을 진행하면서 배운 점을 공유해 보겠습니다.
우선 삽입, 삭제를 하면서 두 노드의 색을 바꿀 일이 많았어서, 두 노드를 포인터로 받아 색을 바꾸는 함수를 만들었습니다. 중복 코드를 함수로 대체하니, 전체 코드가 훨씬 간결해졌습니다. 추후 색을 바꿀 때 버그가 발생하더라도, 함수만 수정하면 되므로 유지보수도 더 수월해질 것이라고 생각합니다.
또한 두 자식이 있는 노드를 삭제할 때, 처음에는 계승자의 값을 삭제될 노드에 복사하고, 계승자를 삭제하는 방식으로 구현했습니다.
하지만 실제 트리에는 노드에 숫자뿐 아니라 다양한 데이터가 저장될 수 있습니다. 데이터의 양이 많아질수록 복사에 드는 비용도 커지게 될 것이라 생각했습다. 따라서 값의 복사 없이, 포인터만을 조정하여 노드 간 연결 관계를 바꾸는 방식으로 코드를 개선했습니다.
마지막으로, 기존엔 parent나 left, right 멤버를 일일이 참조해 삼촌이나 조부모 노드를 확인했습니다. 이를 삼촌, 조부모 포인터를 만들어 사용하는 방식으로 수정했습니다. 다른 누군가가 이 코드를 봤을 때, 직관적인 변수명을 보고 해당 노드가 삼촌이나 조부모라는 점을 바로 파악할 수 있으니, 코드의 가독성이 한층 더 좋았습니다.
어니스트 헤밍웨이는 "모든 초고는 쓰레기다"라는 명언을 남겼습니다. 코드도 마찬가지입니다. 하지만 리팩토링만 잘 한다면 그 쓰레기도 원석이 될 수 있습니다. 감사합니다.