Posted on

코드 컨벤션을 만들기 전에 했어야 하는 질문

들어가며

육아 휴직이 이제 거의 4개월이 다 되어 간다. 여유가 많지는 않지만 그래도 회사를 나갈 때보다는 돌아볼 시간이 있는 것 같다. 무의식 중에도 과거를 돌아보는지 문득 문득 그때 어떤 선택을 했어야 했는지 자꾸 생각이 난다.

어디까지 코드의 규칙을 정할 것인가 하는 것도 바로 그런 지점에서 답을 찾고 있는 질문이었다. 코드 컨벤션 조차 없는 프로젝트에서 일하다보면 누구나 지켜야 하는 강제적 규칙이 있으면 좋겠다는 생각이 든다. 인덴트의 규격도 그렇고, 아예 인덴트 자체를 일정 depth 이상 못가게 했으면 좋겠다는 생각도 든다. 작년의 나 또한 새로 담당하게 된 서비스에서 레거시 코드에 허덕이며, 어떤 규칙을 세워야 이런 고통에서 벗어날 수 있을지 고민이 참 많았다.

규칙 만들기 시행착오

말로 협의하는 코드의 규칙이 지켜질 리가 없다는 것은 경험으로 충분히 익혔기에 아예 빌드에서 실패하는 규칙을 만들려고 했다. SonarQube와 같은 정적 코드 분석이나, 테스트 코드를 추가하고 커버리지에 대한 검증을 Jacoco로 추가할까도 고려해봤고 보안팀에서 쓰고 있는 솔루션도 고려를 해봤다.

그러나 정적 분석을 도입하는 것은 그 설정 자체보다 인프라 팀의 협조가 필요했고, 테스트 커버리지 측정은 테스트 코드를 팀원에게 학습시키는 러닝 커브가 꽤나 가파르다는 것을 사내 스터디를 통해 느꼈다. 이미 사용하고 있는 솔루션은 빌드 시점에 플러그인 형태로 넣기도 어려울뿐더러 전수 조사를 하는 데 몇 시간씩이 소요되어서 아예 사용이 불가능했다.

나 스스로 할 수 있는 것은 없을까 하다가 찾은 것은 checkstyle 이었다. 그러나 이것도 결국 도입에 실패했는데, 일단 어떤 것을 설정하고 어떤 설정을 뺄지를 배워야 하는 문제가 있었고, 여기서 위반한 규약을 팀원들이 이해하기 편하게 보여주는 것은 또 다른 문제였다. 기본 리포트는 위반한 규칙에 대한 클래스 명을 나열하는 식이라서 이를 통해서 이해하고 수정하기에는 기능 개발 자체보다 더 큰 비용이 필요할 수도 있겠다는 생각이 들었다.

이 모든 것 보다 더 큰 문제는 팀원들이 규율로써 코드의 제약이 생긴다는 것에 대한 불안이었다. 그 지점이 checkstyle 자체를 내가 더 파보고 마이너한 문제를 개선해보려는 노력을 투입하기를 꺼리게 만들었다.

했어야 하는 질문

그 당시의 내가 주로 했던 질문은 이거였다. ‘규칙의 강제는 어떻게 ‘적절히’ 행할 수 있을까? 어느 층위까지 강제 해야 하고, 각 층위에서 얼마나 상세하게 규칙을 짜야할까? 모든 걸 똑같이 하도록 강제하면 도움이 될까?’ 지금 생각해보면 이 질문을 했어야 하는 것 같다. ‘규칙을 내세워서 나는 무엇을 해결하려 했을까?’

읽고 이해하는 데만 한참이 걸리는 레거시 코드들에 파묻혀 이러지도 저러지도 못하는 경험이 누구나 한번은 있었으리라 생각한다. 나는 그런 상황을 벗어나고 싶었다. 그것을 위해 코드를 읽고 이해하는 비용을 줄이고 싶었던 것 같다. 그걸 줄여야 더 본질적인 활동 즉, 설계나 구현에 집중할 수 있으니까.

이렇게 시작하면 다시 이런 질문을 이어갈 수 있다. 읽는 비용이란 것이 무엇인가? 이 코드는 이해하기가 어려운 것인가, 보기에 지저분한 것인가? 다시 말하면 선호하는 인덴트, 개행, 띄어쓰기, 참조하지 않는 메서드나 클래스가 문제인가? 아니면 통일되지 않은 레이어, 의도를 알기 어려운 네이밍의 변수나 메서드, 의도를 곡해하는 네이밍이 문제인가?

이렇게 보았을 때 내가 형식의 문제와 의도의 문제를 한번에 해결하려고 하니까 어려웠다는 것을 알았다. 그리고 팀원들에게도 더 쉽게 해소할 수 있는 부분과 협의를 통해서 해야 할 부분을 나누어서 이야기할 수 있었겠다는 생각도 든다.

고칠 방향을 모를 땐 재는 것부터

행복한 가정은 모두 비슷하지만, 불행한 가정은 저마다의 이유로 불행하다. — 톨스토이, 『안나 카레니나』

건강한 코드베이스는 대체로 비슷하다. 계층이 지켜지고, 경계가 있고, 자주 바뀌는 곳과 안 바뀌는 곳이 나뉘어 있다. 반면 망가지는 방식은 저마다 다르다. 어떤 곳은 컨트롤러가 비대하고, 어떤 곳은 엔티티가 전 계층을 돌아다니고, 어떤 곳은 순환 참조가 얽혀 있다.

checkstyle 설정에서 막혔던 것은 이런 측면의 문제 또한 역할을 했을 수 있다. 남이 만들어둔 룰셋은 건강한 코드의 일반형을 기술한 것이라서, 우리 코드의 문제를 효과적으로 해소하는 방법은 알려주지 않기 때문이다. 우리 코드의 문제가 무엇인지 나에게 더 명확한 근거가 있었다면 일단 시도까지는 어렵지 않았을 수도 있을 것 같다.

명확한 근거는 어떻게 만들어 볼 수 있을까? checkstyle이나 Jacoco 등은 대개 기준선을 체크만 하는 기능이 있다. 현재 시점의 위반 개수와 종류를 기록만 하고 빌드를 제약하는 규칙으로는 활용하지 않으면 된다.

  • checkstyle
checkstyle {
    maxWarnings = 137  // 현재 위반 수를 그대로
    maxErrors = 0
}
  • jacoco
jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                counter = 'LINE'
                minimum = 0.42  // 현재 수치
            }
        }
    }
}

특히 유용해 보이는 것은 ArchUnit의 FreezingArchRule이라는 것이다. 이 방식은 규칙을 감싸서 돌리면 지금 있는 위반을 전부 파일로 적어두고, 그 다음부터는 새로 생긴 위반만 빌드를 실패하게 한다. 기존 위반이 하나 고쳐지면 적어둔 목록에서 알아서 빠지기 때문에 목록은 계속 줄어든다.

이 목록을 통해 계층 위반이 몇 건이고 엔티티 노출이 몇 건인지 나오면, 그때 비로소 어떤 규칙이 우리에게 필요한지 고를 수 있다.

어제까지 멀쩡했던 내 코드가 오늘 출근했더니 빌드가 안 된다면 당연히 그러한 규칙의 저항은 거셀 수밖에 없다. (나같아도 AFK를 시전할지도 모르겠다..) 그런 측면에서 이 방식은 바로 지켜야 할 규칙을 강제하는 것보다는 팀원들의 불안도 일정 부분 해소할 수 있다.

규칙을 만들고 싶어질 나에게

새로운 규칙을 강제하고자 하는 관리자가 있다면 내가 듣고 싶은 것은 앞으로 지켜야 할 규칙 자체보다는 어떤 측면 때문에 그러한 선택을 했는지, 거기에 따르는 트레이드오프는 무엇인지 등일 것 같다. 그리고 바로 그 지점이 동시에 내가 규칙을 만들려고 할 때 해야 하는 지점일 듯하다.

레거시를 유지보수 하는 고통은 분명 나에게 실재하기 때문에 빠르게 규칙으로 해소하고 싶어질 수 있다. 가장 빠르고 효과적으로 규칙을 만드는 것은, 그러한 고통을 타인도 충분히 이해할 수 있게끔 정량적, 정성적으로 정리하고 공유하고 대화하며 맞춰가는 것일 것이다. 다시 복직을 했을 때 규칙을 만들고 싶은 시점에 즈음하여 이 글을 다시 읽어봐야겠다.