ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 소스코드에 주석은 어느 정도로 활용되어야 할까?
    Today I Learned 2023. 5. 26. 03:38

     

    (해당 글은 저서 'Docs for Developers'를 참고하여 작성했으나, 다소 주관적인 견해를 담고 있습니다. 다른 견해가 있으실 경우 자유롭게 제안해주시면 감사하겠습니다.)
     

    시간이 지나면 나조차도 내 코드를 이해하기 어렵게 된다.

    프로젝트를 진행하면서 가졌었던 생각 중 하나로, 주석을 아예 작성하지 않고 단지 코드를 읽는 것만으로도 이해할 수 있는 소스코드를 작성할 수 있어야 한다는 생각이 있었다. 이를 위해 코드를 작성할 때 줄곧 다음과 같은 것들을 고려했었다.
     

    - 축약어를 사용하지 않는다.  ex) a, b, i, idx, cnt, ...
    - 변수, 메서드 등을 네이밍할 때는 의미를 최대한 나타낸다.

     
     
    이 규칙들을 지킨다고 해서 항상 읽기 좋은 코드가 만들어지지는 않았다. '의미를 최대한 나타내기 위해' 선택하는 단어가 항상 의도한 바를 정확하게 드러내는 단어가 아닌 경우가 많았다. 구현을 진행하면서 주어진 시간을 생각하지 않을 수 없었기 때문에, 비슷한 의미를 나타내는 여러 단어들 중 그 의미를 나타낼 수 있는 최적의 단어가 아닌 단어를 선택하는 경우가 많았기 때문이다. 그리고 설계 여하에 따라 특정 상황을 네이밍으로 나타내기 위해 변수나 메서드의 네이밍이 길어지는 경우가 발생하기도 했다.
     
    그럼에도 불구하고, 일단은 소스코드 자체는 어떻게든 만들어졌다. 다음 소스코드는 개인 프로젝트를 진행하던 당시 작성했던 게시글 전체를 불러오는 메서드를 가져온 것이다.
     

    // services/post/GetPostsService.java
    
    public PostsDto findAll(Long currentUserId) {
        User currentUser = currentUserId == null
            ? null
            : userRepository.findById(currentUserId)
            .orElseThrow(() -> new UserNotFound(currentUserId));
    
        List<Post> Posts = postRepository.findAll();
    
        List<PostListDto> postListDtos = Posts.stream()
            .map(post -> {
                Game game = gameRepository.findByPostId(post.id())
                    .orElseThrow(GameNotFound::new);
    
                Place place = placeRepository.findById(game.placeId())
                    .orElseThrow(() -> new PlaceNotFound(game.placeId()));
    
                List<Register> registers
                    = registerRepository.findAllByGameId(game.id());
    
                Boolean isAuthor = post.isAuthor(currentUser);
    
                Integer currentMemberCount = game.countCurrentMembers(registers);
    
                Register myRegister = game.findMyRegister(currentUser, registers);
    
                Long registerId = myRegister == null
                    ? null
                    : myRegister.id();
    
                String registerStatus = myRegister == null
                    ? "none"
                    : myRegister.status().toString();
    
                GameInPostListDto gameInPostListDto
                    = game.toGameInPostListDto(
                    currentMemberCount,
                    registerId,
                    registerStatus
                );
    
                PlaceInPostListDto placeInPostListDto
                    = place.toPlaceInPostListDto();
    
                return post.toListDto(
                    isAuthor,
                    gameInPostListDto,
                    placeInPostListDto
                );
            })
            .toList();
    
        return new PostsDto(postListDtos);
    }

     
     
    마지막으로 코드를 수정한 지 5개월 가량이 지나가는 지금, 코드를 수정하기 위해 맥락을 파악해야 한다고 가정해보고, 소스코드가 어떻게 기능하는지 코드를 하나하나 뜯어보면서 코드의 맥락을 이해하는 데 걸린 시간을 측정해보았다.
     

    사용자 식별자가 전달되었으면 사용자 객체를 찾고, 그렇지 않으면 null로 둔다.
    모든 게시글 객체를 찾는다.
    찾은 게시글 객체 각각에 대해, 경기, 경기가 진행되는 장소, 참가자들을 찾는다. 참가 인원을 식별한다.
    접속한 사용자가 게시글 작성자인지 식별하고, 접속한 사용자의 신청 상태를 식별한다.
    신청 상태의 존재 여부에 따라 신청 정보 상태 리소스를 생성한다.
    찾은 정보들과 신청 정보 리소스를 이용해 게시글 목록 내 개별 정보 리소스로 변환한다.
    변환한 리소스들을 컬렉션으로 반환한다.

     
     
    다른 소스코드로 넘어가지 않고 해당 소스코드만을 보면서 구체적인 맥락을 이해하려 했을 때, 대략 20분 정도가 소요되었다.
     
    내가 작성한 코드였고, 나한테는 그래도 저 소스코드가 무엇을 목적으로 하는 동작인지 대략적인 맥락이 존재했음에도 위와 같이 동작의 구체적인 흐름을 파악하는 데 적지 않은 시간이 걸렸다. 나조차도 해당 코드를 파악하는 데 이 정도의 시간과 에너지를 사용해야 했음을 고려한다면, 해당 도메인에 대한 이해가 충분하지 않은 상태의 다른 개발자들이 내 코드를 수정하기 위해 코드를 들여다본다면 코드의 흐름과 코드에서 출현하는 도메인을 이해하기 위해 훨씬 더 많은 시간을 사용해야 할 것임을 짐작할 수 있다.
     
     

    코드만으로는 담기 어려운 맥락을 주석으로 전달하자.

    저서 'Docs for Developer'에서는 좋은 코드 주석에 대해 설명하면서 코드 주석을 개발 과정에서 어떻게 잘 사용할 수 있을 것인지 제시한다.
     

    좋은 코드 주석은 다음과 같은 특징을 갖습니다.
    - 간결합니다.
    - 코드와 관련된 설명을 담습니다.
    - 자유롭게 사용하되 지나치게 사용하지 않습니다.
    코드 주석은 자신이 작성한 코드가 무엇을 하는지 설명하는 것을 넘어서 설계 결정 사항과 코드 작성 시 택한 트레이드오프를 문서화함으로써 여러분이 무슨 일을 했고 왜 그렇게 했는지 설명해 줍니다.

    ... 코드베이스가 진화할수록 과거에 내린 결정의 맥락을 남겨두는 것이 도움이 됩니다.

     
     
    소스코드 한 줄 한 줄 자체는 읽기 쉬운 코드를 작성하기 위한 원칙에 의거해 작성할 수 있겠으나, 각 줄의 소스코드들이 모여 형성된 전체 소스코드의 맥락을 이해하기 쉽게 전달하는 것을 코드 작성 원칙만으로 커버하기에는 부족한 감이 있다.
     
    테스트 코드를 참고하면 특정 메서드나 모듈이 어떤 값들을 전달받아 어떤 결과를 도출하는지 이해하는 데 도움을 받을 수 있지만, 테스트 코드 자체도 소스코드이기 때문에 읽어서 이해하는 과정이 필요하고, 입력을 이용해 생성되는 출력이 '어떤 흐름을 거쳐' 생성되는지를 테스트 코드만으로 완벽하게 파악하기에는 어려울 수 있다. 앞서 언급한 두 방식만으로는 메우기 어려운 '설계한 로직 자체를 이해하는 영역'을 메우는 데 주석을 활용할 수 있을 것이라는 생각이다.
     
     
    다만 주석은 사용할 때 단점이 확실한 방식이니만큼 주의할 필요가 있다. 주석을 너무 맹신해서 코드 작성 원칙을 전혀 고려하지 않고 아무렇게나 코드를 작성한 뒤, 주석만으로 소스코드의 이해를 뚝딱 해결하려 하는 방식은 지양해야 할 것이다. 어째서인가?
     
    주석의 가장 큰 단점은 주석이 소스코드 내에서 문법이 존재하지 않는 단순 텍스트라는 데 있다. 아무리 소스코드의 내용이 바뀌어도 주석 때문에 컴파일이나 빌드 과정에서 문제가 생길 일은 없다. 이를 바꿔 말하면, 주석에 존재하는 오류는 주석을 작성한 개발자가 직접 주석의 내용을 일일이 확인하면서 찾아내야 한다는 이야기가 된다. 소스코드의 이해를 주석에 완전히 의존하게 되면 소스코드가 커지면 커질수록 이를 설명하기 위한 주석의 크기도 커져야 하고, 개발자가 소스코드와 주석을 대조하며 수정하는 과정에서 수정이 누락되는 부분이 얼마든지 발생할 가능성이 생겨나게 된다. 이러한 주석과 실제 소스코드의 차이는 이를 읽는 사람으로 하여금 잘못된 이해를 하게 할 가능성이 높아지게 만들 것이므로, 이런 식으로 주석을 활용하게 된다면 주석으로 인해 오히려 소스코드의 품질에 좋지 않은 영향이 생길 가능성이 높아질 것이다.
     
    이러한 문제점들을 고려해, 추후 소스코드를 작성하는 과정에서 주석을 활용할 경우에는 다음의 요지들를 고려하면서 주석을 작성하는 것을 목표해보려 한다.
     

    - 주석은 가급적 소스코드의 정해진 영역에 몰아서 작성한다. 적은 양의 주석을 소스코드 이곳저곳에 흩뿌려놓지 않는다.
    - 다소간의 설명이 필요한 동작 흐름과, 그러한 흐름으로 동작을 작성한 이유를 설명하는 것을 주석 작성의 주된 목표로 한다. 단순히 소스코드의 한 줄 한 줄을 일일이 주석으로 설명하는 것은 지양한다.

     
     
     

    References

    - Docs for Developers (p.54-55)
     
     
     
     

    댓글

Designed by Tistory.