ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리팩토링에 대하여
    프로젝트/미디어 스트리밍 서버 프로젝트 2022. 10. 8. 00:01
    728x90

    개요

    1주차 스프린트에서는 한번도 해보지 못했던 미디어 서버를 구축하기위해 달렸습니다.

     

    또한 그 과정속에서 Git에 익숙해지며, Covention을 정하는 등 공동의 목표를 위한 계획을 세워나갔습니다.

     

    그러나 기능 구현에 포커스를 맞춘 만큼 코드 품질은 조금 떨어지게 되었습니다.

     

    하지만 이러한 점을 인지한 상태로 기술 부채를 쌓아나갔으며 스프린트 2에서는 코드 품질을 향상하고자 합니다.

     

    다음과 같은 문제점들을 인지하고 있었습니다.

    • 부적합해 보이는 변수명/함수명
    • 너무 많은 일을 하는 메서드
    • 떨어지는 가독성
    • 반복 되는 코드
    • 테스트하기 어려운 코드
    • 부족한 테스트 코드

     

    리팩터링이 필요한 이유는?

    고객의 모든 요구사항을 예측하고 논리적으로 완벽한 소프트웨어를 단번에 설계하는 것은 거의 불가능에 가까운 일입니다.

    따라서 더 나은 서비스를 위해 끊임없이 고민하고 개선하는 것이 개발 조직의 존재 이유라고 할 수 있습니다.

    디자인 스테미너 이론

    디자인 스테미너 이론에 따르면 다음과 같습니다.

    품질이 낮은 소프트웨어는 시간이 지나면서 많은 기능을 가질수록 새로운 기능을 추가하는데 시간이 많이 걸리게 됩니다.

    품질이 높은 소프트웨어는 기능을 추가하는데 더 낮은 시간만으로 해결할 수 있습니다.

     

    낮은 품질의 소프트웨어는 우리의 시간을 지속적으로 소모시키며 장기적인 관점에서 보았을 때 경제적으로 더 손해를 보게 됩니다.

     

    실제로 동영상 업로드에 대한 동기 -> 비동기로 전환하는 과정에서 복잡하게 얽혀있는 로직들을 따라가며 전환하면서 시간적으로 오래 걸리는 경험을 하고 리팩터링의 필요성에 대해 더 느끼게 되었습니다.

     

    따라서 리팩토링을 통해 코드의 품질을 높여야 더 많은 기능(고객의 요구사항)을 빠르게 적용할 수 있습니다.

     

    리팩터링을 위한 준비 과정

    기존의 코드가 제대로 동작하고 있는데 리팩터링 이후에 제대로 동작하지 않을 수 있습니다.

    이런 과정을 일일히 반복적으로 검사하는 과정을 지루합니다.

     

    따라서 기존의 코드에 테스트 코드를 작성하여 모든 기능이 성공적으로 동작하는지 검사하고, 리팩터링을 거친 이후에도 테스트 코드를 통해서 모든 기능이 성공적으로 동작하는지 검사할 수 있습니다.

     

    1단계 테스트하기 쉬운 코드로 개선하기

    리팩토링을 위해서 테스트 코드를 작성하려고 할 때 테스트 코드를 작성하기 어려운 경우가 존재할 수 있습니다.

     

    테스트하기 어려운 코드

    1. 같은 입력에 항상 같은 결과를 반환하지 않는 코드(랜덤 값 반환 또는 DB 조회)

    2. 외부 상태를 변경하는 코드(DB 값 저장, 화면 출력)

     

    다음 과정을 거쳐 테스트 하기 쉬운 코드로 개선합니다.

    1. 테스트하기 쉬운 코드와 어려운 코드를 분리

    2. 분리한 두 코드는 최대한 가장자리에서 만나게 합니다.

    3. 자동 테스트의 비용이 크다면 수동 테스트를 진행합니다. 이때 자동 테스트는 Mock을 활용하여 수행할 수 있습니다.

     

    예시)

    @Service
    @Transactional
    class MetadataService(
        val metadataRepository: MetadataRepository,
    ) {
        var ffProbePath: String = Loader.load(ffprobe::class.java)
    
        fun registerMetadata(file: MultipartFile, convertFile: File, video: Video): Metadata {
            val ffProbe = FFprobe(ffProbePath)
            val probe: FFmpegProbeResult = ffProbe.probe(convertFile.absolutePath)
            convertFile.delete()
    
            val metaData = Metadata(
                width = probe.getStreams()[0].width,
                height = probe.getStreams()[0].height,
                duration = probe.getStreams()[0].duration.toInt(),
                video = video
            )
            return metadataRepository.save(metaData)
        }    
    }

    registerMetadata 메서드를 테스트 하려고 했을 때 어려움이 있었습니다.

     

    우선 파일 업로드의 프로세스는 다음과 같습니다.

    val convertFile: File = ThumbnailGenerator.generateThumbnail(file) //비디오에서 썸네일 추출
    val savedVideo = videoService.upload(request, file) //S3와 DB에 썸네일, 비디오 저장
    val savedMetaData = metadataService.registerMetadata(file, convertFile, savedVideo)

    썸네일을 추출하는 과정에서 MultipartFile객체를 File객체로 만들어 낸 후 추출합니다.

    또한 이때 File객체의 (./"파일이름")가 만들어지게 됩니다.

     

    이후에 registerMetadata가 만들어진 File객체를 사용하기 때문에 위에 작업에 의존적입니다.

     

    사실 registerMetadata 메서드가 하는 일을 엄청나게 많습니다.

    1. 파일로부터 메타데이터를 추출

    2. 파일 삭제

    3. 메타데이터 저장

     

    이를 내포하는 메서드의 이름을 다시 지어보면 extractMetadataAndDeleteFileAndRegisterMetadata라는 이름이 나오게 됩니다.

     

    따라서 1,2번에 해당하는 일은 해당 메서드에서 제거하고 대신 이 과정을 파일 업로드의 프로세스에 포함시킵니다.

    val convertFile: File = ThumbnailGenerator.generateThumbnail(file)
    val probe: FFmpegProbeResult = FFprobe(ffProbePath).probe(convertFile.absolutePath) //추가됨
    
    val savedVideo = videoService.upload(request, file)
    val savedMetaData = metadataService.registerMetadata(probe, savedVideo) //인자 변경됨
    convertFile.delete() //추가됨

     

    이제 registerMetadata는 순수하게 메타데이터만 저장하는 일을 수행하고 테스트하기 쉬워졌습니다.

    물론 DB에 저장하는 작업이기 때문에 쉽다고 할 순 없지만 Mock을 통해 테스트를 진행할 수 있습니다.

     

    2단계 테스트 코드 작성

    시간적 여유에 따라 단위테스트, 통합 테스트를 작성하여 코드가 제대로 동작하는지 검증합니다.

     

    3단계 리팩토링 수행

     

     

     

    출처

    https://teamdable.github.io/techblog/SoC-to-IoC\

    https://hudi.blog/layered-architecture/

    https://www.youtube.com/watch?v=Cz_a2gQp63c 

    https://www.youtube.com/watch?v=mNPpfB8JSIU&t=103s 

     

    댓글

Designed by Tistory.