소프트웨어는 첫 배포로 끝나는 법이 없습니다. 기능은 계속 추가되고 요구사항은 끊임없이 변하기 마련이죠. 처음에 상상하지 못했던 방향으로 서비스가 커지기도 합니다. 이때 코드가 조금만 엉켜 있어도 문제가 생깁니다. 작은 수정 하나가 연쇄적인 수정으로 번지게 되니까요.
그래서 유지보수성은 선택 사항이 아닙니다. 소프트웨어가 계속 살아남기 위한 필수 조건에 가깝습니다. 코드를 짤 때 가장 먼저 고려해야 할 부분입니다. 당장 잘 돌아가는 코드보다 미래를 견디는 코드가 더 가치 있습니다.
코드는 엉키지 않고 단단해야 합니다
유지보수가 가능한 코드를 작성하는 목적을 다시 생각해 봅시다. 지금 당장 오류 없이 실행되는 것만이 전부는 아닙니다. 앞으로 바뀔 수밖에 없는 코드를 견딜 수 있게 만드는 것이 중요합니다. 코드가 단단하게 엉켜 있으면 수정이 정말 어렵습니다.
하나를 고치려다가 전체를 건드려야 하는 상황이 오기 쉽습니다. 이런 상황을 피하려면 코드 사이의 연결을 느슨하게 만들어야 합니다. 그래야 변경 사항이 생겨도 유연하게 대처할 수 있습니다. 이것이 우리가 설계를 고민해야 하는 이유입니다.

DI를 사용하는 진짜 목적을 이해해야 합니다
이런 맥락에서 DI(Dependency Injection)라는 개념이 등장합니다. 많은 프로그래머가 이를 무조건 써야 하는 정답으로 여기곤 합니다. 하지만 DI는 목적이 아니라 수단일 뿐입니다. 왜 사용하는지 모른 채 형태만 따라 하면 안 됩니다.
이유 없이 사용하면 코드만 복잡해지고 문제는 그대로 남습니다. DI가 의미를 갖는 이유는 아주 명확합니다. 바로 유지보수성에 중요한 느슨한 결합(Loose Coupling)을 만들어 주기 때문입니다. 이 개념을 이해하는 것이 우선입니다.
서로에 대해 너무 많이 알면 곤란합니다
느슨한 결합이란 한 코드가 다른 코드의 속사정을 모르게 하는 것입니다. 어떤 객체가 무엇을 한다는 사실만 알면 충분합니다. 어떻게 만들어졌는지 세세한 내용까지 알 필요는 없습니다. 내부 사정까지 알게 되면 두 코드는 꽉 묶여 버립니다.
이렇게 결합도가 높아지면 구현을 바꿀 때 문제가 생깁니다. 그 구현을 사용하는 모든 코드가 함께 흔들리기 때문이죠. 반대로 결합이 느슨하면 내부는 바뀌어도 바깥은 조용합니다. 서로 영향을 적게 주고받는 것이 중요합니다.
내부가 바뀌어도 외부는 안전해야 합니다
이 차이가 바로 확장 가능성(Extensibility)을 만듭니다. 내부 구현이 바뀌어도 외부에서는 거의 영향을 받지 않게 되니까요. 확장이 쉬워야 유지보수도 수월해집니다. 새로운 기능을 넣을 때 기존 코드를 뜯어고치지 않아도 됩니다.
그래서 자주 인용되는 원칙이 하나 있습니다. 바로 구현이 아니라 인터페이스에 프로그래밍하라는 말입니다. 이 부분에서 많이들 막히시죠? 어렵게 생각할 필요 없습니다. 구체적인 내용보다 추상적인 개념에 의존하라는 뜻입니다.
구체적인 구현보다 역할에 집중하세요
이 말은 인터페이스라는 문법 요소를 많이 쓰라는 의미가 아닙니다. 이 객체는 어떤 역할을 수행한다는 사실까지만 의존하라는 이야기입니다. 그 역할을 어떻게 구현했는지는 바깥에서 정해주면 됩니다. 이렇게 짜임새를 잡으면 훨씬 유연해집니다.
새로운 구현이 추가되거나 기존 구현이 교체되어도 괜찮습니다. 이를 사용하는 코드는 거의 손대지 않아도 되니까요. 역할과 구현을 분리하면 변경에 대한 부담이 줄어듭니다. 이것이 우리가 지향해야 할 방향입니다.
DI가 변경의 파급 범위를 줄여줍니다
DI는 이 원칙을 현실적으로 가능하게 만들어 줍니다. 객체가 스스로 필요한 구현을 직접 생성하지 않습니다. 대신 외부에서 주입받도록 하면 연결 고리는 훨씬 느슨해집니다. 테스트에서는 가벼운 가짜 객체를 넣을 수도 있죠.
기능을 확장할 때도 기존 코드를 건드리지 않고 새로운 구현을 추가할 수 있습니다. 이때 DI의 가치는 편리함에 있는 것이 아닙니다. 변경이 일어났을 때 그 파급 범위를 국소화할 수 있다는 데 있습니다.
미래의 변경에 대비하는 질문을 던져보세요
유지보수가 쉬운 코드는 미래의 변경을 완벽히 막아 놓은 코드가 아닙니다. 오히려 변경이 일어날 것을 전제로 그 영향을 최소화한 코드입니다. 느슨한 결합은 그 설계를 위한 훌륭한 밑바탕이 됩니다. 그리고 DI는 이를 돕는 실용적인 도구입니다.
DI를 쓸지 말지 고민하기 전에 먼저 질문을 던져보세요. 이 코드는 앞으로 바뀔 때 얼마나 적은 부분만 고치면 될까요? 그 질문에 대한 답을 찾는 과정이 더 나은 코드로 이끌어 줄 겁니다. 변경에 유연한 코드가 살아남는다는 사실을 기억하세요.