Logo

깨진 유리창의 법칙

broken windows

깨진 유리창의 법칙(Broken Windows Theory)을 들어보셨나요? 깨진 유리창을 방치하면 그 지점을 중심으로 범죄가 확산된다는 사회학 이론인데요. 이미 낙서가 되어 있는 화장실 벽에는 왠지 낙서를 더 해도 괜찮을 것 같은 느낌이 들거나, 누군가가 무심코 버린 작은 쓰레기 주변에 금세 쓰레기 더미가 쌓이는 모습은 깨진 유리창의 법칙의 대표적인 예입니다. 최근 많이 들리는 미국 캘리포니아 주에서 경범죄에 대한 처벌을 약화시킨 결과, 유명 브랜드들이 사업을 철수할 정도로 치안이 매우 좋지 않아졌다는 뉴스도 깨진 유리창의 이론의 좋은 예일 것입니다.

사실, 깨진 유리창의 법칙은 소프트웨어 개발에서도 어렵지 않게 목격되는데요. 예전에 잦은 애플리케이션 버그로 큰 곤경을 겪고 있는 개발팀의 매니저가 SOS를 쳐서 긴급 투입된 적이 있습니다. 제가 좀 들여다보니 상황이 정말 심각하더군요. 버그가 잦다는 프로젝트의 테스트 코드는 CI/CD를 통해 자동으로 돌릴 수 없도록 처참하게 망가져 있었습니다. 통과하는 테스트 케이스보다 실패하는 테스트 케이스가 더 많아서 사실 테스트를 돌려봤자 큰 의미가 없는 상황이었습니다. 팀원들은 이미 테스트가 실패하는 PR을 승인하고 배포하는 데 익숙해져 있었고, 최근 커밋(commit) 이력을 보니 아예 테스트 작성을 포기한 듯 보였습니다. 자동화 테스트가 제대로 이뤄지지 않는 상황에서 버그가 자주 발생하니, 자연스럽게 개발자들은 구현한 기능을 직접 수동으로 테스트하는 데 많은 시간을 쓰고 있었습니다. 이는 단순한 소프트웨어 품질 문제를 넘어서 심각한 개발 생산성 문제로 이어질 수 있다는 생각이 들었습니다.

이 문제에 대해서 팀 내 개발자들과 한 명씩 1on1 미팅을 해보니, 처음부터 이렇게 테스트 코드가 엉망은 아니었다고 합니다. 그런데 한 가지 매우 흥미로운 점은 이 모든 사태가 불안정한 테스트 케이스 하나로 부터 시작되었다는 거죠… 이 소위, Flaky한 테스트 케이스는 어쩔 때는 통과하고 어쩔 때는 실패해서 많은 개발자들에게 큰 스트레스를 주었다고 합니다. 코드를 머지(merge)하고 배포하기 위해 테스트가 모두 성공할 때까지 여러 번 반복해서 실행해야 했으니까요. 제가 구구절절히 설명하지 않더라도 자신이 수정한 코드와 전혀 관련없는 테스트가 실패하면 얼마나 짜증나는지는 겪어보신 분들은 아마 잘 아실 겁니다.

그러다가 어느 날 한 개발자가 도저히 못참고 해당 테스트 케이스만 빼고 테스트가 돌아가도록 하였습니다. (파이썬이나 자바스크립트 개발자라면 해당 테스트 케이스에 skip() 함수를 달았다고, 자바 개발자라면 @Ignore 어노테이션을 달았다고 상상하시면 되겠습니다.) 곧 다른 개발자들도 불안정한 테스트 케이스가 나타날 때마다 이를 따라하기 시작했습니다. 그리고 이러한 조치를 Quarantining tests라는 그렇듯한 용어로 부르며 Band-aid solution, 즉 임시 방편을 정당화하였습니다. 마치 코로나 확진자를 격리시키듯이 불안정한 테스트를 격리시키기 시작했지만, 마치 코로나 때처럼 점점 더 많은 확진자가 생겨났습니다. 결국 개발자들은 테스트를 돌려도 본인의 코드가 상용 환경에서 잘 작동할 것이라는 확신할 수 없는 지경에 이르게 됩니다. 자연스럽게 수동 테스트에 더 의존하게 되고, 자동화 테스트를 위한 테스트 코드는 덜 작성하는 악순환의 고리에 빠지게 됩니다.

그 프로젝트의 망가진 테스트가 정상화될 때까지 저는 팀과 상당히 오랜 시간을 함께 해야했습니다. 우선 팀 전체가 이 문제를 정확히 인지할 수 있도록 공론화하고 개발자들과 현 상태를 더 이상 방치할 수 없다는 공감대를 형성하였습니다. 무엇보다 테스트를 실행하지 않아 테스트를 작성하지 않는 악순환을 끊는 것이 우선이었습니다. 우선 테스트가 없는 코드에 대해서는 무관용 원칙을 세우고, 실패하는 테스트가 하나라도 있을 경우 아예 코드를 머지할 수 없도록 CI/CD 설정을 변경했습니다. 이를 위해서 초반에는 실패하고 있는 모든 테스트 케이스가 실행되지 않도록 하는 것도 감수해야 했습니다. 그리고 개발자들은 본인이 건드린 코드와 직접적인 관련이 없더라도 어떤 테스트가 실패하면 미루지 않고 즉시 원인을 찾아 고쳐야 했습니다. 상당히 고통스러운 과정이었지만 저는 열심히 개발자들을 독려하였고, 이로 인한 단기적인 퍼포먼스 저하와 팀의 우선순위에 대해서 저를 불러들인 매니저와 적극적으로 소통하였습니다. 얼마나 많은 테스트 케이스가 정상화되는지 팀원들에게 매주 진척도를 공유하였고, 마침내 제외되었거나 깨져있던 100여개의 테스트 케이스를 모두 안정적으로 CI/CD를 통해 자동으로 돌릴 수 있게 되었습니다. 자연스럽게 테스트 커버리지도 80% 이상으로 올라오게 되었죠. 팀은 지긋지긋한 버그로 부터 해방될 수 있었으며, 저도 더 이상 그 팀을 집중적으로 케어할 필요가 없었습니다.

이와 같이 깨진 유리창의 법칙은 소프트웨어 개발에도 그대로 적용됩니다. 만약에 맨 처음에 불안정한 테스트 케이스를 발견한 개발자가 해당 테스트 케이스를 제외하지 않고 불안정한 원인을 찾아서 고쳤더라면 어땠을까요?

대단한 소프트웨어를 만들기 위해서 무언가 대단한 노력을 해야한다고 생각하시는 분들이 많습니다. 그러나 저는 이처럼 사소한 노력들이 모여서 대단한 소프트웨어를 만들어 낸다고 생각합니다. 작은 것들이 무시되어 조금씩 쌓이다 보면, 나중에는 기술 부채가 눈덩이처럼 불어나, 자멸하는 프로젝트를 많이 보았기 때문입니다.

혹시 지금 코드 편집기에서 여기저기에 빨간 노란 밑줄이 그어져 있는데, 귀찮아서 방치하고 계시지는 않으신가요? 악취가 진동하는 코드를 리펙토링(refactoring)하지 않고 그 주변에 더 냄새나는 코드를 덧붙이고 계시지는 않으신가요? 서버를 띄우면 터미널에 갖가지 경고 메시지들이 나오는데, 어찌됐든 서버가 돌아가니 무시하고 있지 않으신가요? 웹사이트를 열면 브라우저 콘솔에 오류가 가득한데, 일반 사용자는 모르겠지 하고 같이 모른 척하고 계시지는 않으신가요? 지금 바로 잡지 않으면 나중에 바로 잡을 수 있을까요? 내가 고치지 않으면 동료가 고칠까요? 이런 기본적인 것들을 소홀히 하면서 자신을 훌륭한 개발자라고 할 수 있을까요?

항상 소프트웨어 개발을 하시면서 주변에 당장 고칠 수 있는 깨진 유리창이 없는지 찾아보세요. 그리고 지금 바로 그걸 바꿔보시면 어떨까요? 여러분의 팀에도 금세 긍정적인 변화가 찾아올 거라 믿습니다. 🙏

제가 캐나다에 와서 처음으로 존경하던 Principal 엔지니어가 제 귀에 못이 박히게 했던 말로 이 글을 마무리하겠습니다.

Always leave it better than you found it.