ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [용어] 코드 리뷰
    Front-end 개발 2023. 7. 27. 17:48

    목차

    1.요약

    2. 개발자 문화와 코드 리뷰

    3. 코드 리뷰는 효율적인가?

    4. 코드리뷰 절차와 양식

    5. 방법론

    6. 사용되는 도구


     

    GitHub Repository 초대장

    7월 27일 오늘 코드리뷰 스터디를 가입하고 Gihub Repository 초대를 받았다.

    어렴풋이 코드리뷰란 것은 화이트보드나 빔프로젝트를 켜고 자신이 개발한 코드를 동료들에게 발표하고 피드백을 주고 받는 시간이라고 이해하고 살고 있었다.

    이제 IT 회사에 들어가기 전 그리고 멋쟁이사자처럼 프론트엔드 스쿨7기 동기들에게

    형식을 맞춘 양식과 적절한 코멘트를 위해서 코드 리뷰에 대해 좀 더 알아보고 참여하고자 한다.

     

    1. 요약

    1-1. 내 맘대로 한 줄 요약

    내가 코드리뷰에 대해 찾아 읽어보고 한 문장으로 요약하면,

    코드의 생성산과 안정성을 지키면서 빠르게 개발하는 개발 문화를 코드 리뷰라고 한다.

     

    구글에서는 작성된 코드에 대해 100% 코드리뷰,

    마이크로소프트에서는 개발자 업무 50%는 개발, 50%는 코드 리뷰,

    네카라쿠배를 포함해 국내 스타트업은 "리뷰는 당연하다" 는 인식과

    코드 Merge 전 코드 리뷰는 필수적인 프로세스가 되었다.

     

    1-2. 코드 리뷰를 하면 생기는 장점

    - 활발한 의견 교류와 통합

    - 버그의 조기 발견

    - 개발 표준 준수를 통한 일관된 스타일 유지

    - 중복 코드 방지

    - 모듈의 재사용성 증대

    - 잘 만들어진 코드를 보며 성장할 수 있는 기회

    - 전체적인 조직의 역량 강화

     

    위와 같은 IT 업계의 인식과 장점을 가진 코드 리뷰를 본격적으로 살펴보기 전에

    코드 리뷰에서 사용하는 기본적인 용어와 구성 시나리오를 이해하고 넘어간다.

     

    기본적인 용어

    용어 뜻, 용도
    pr Pull Request.
    Git/Github 에서 사용자가 원격 저장소에 push 하여 새로운 사항이 적용됬을 때, 다른 사용자에게 push 된 상황을 알리는 것.
    CL Change List.
    변경 요구사항. 소스 코드 관리에 반영되었거나 코드 리뷰 진행중인 코드 집합체 단위
    LGTM Looks Good To Me.
    코드 리뷰를 승인할 때 리뷰어가 사용
    Nit Nitpick.
    고치면 좋지만 아니어도 그만인 코드로 건설적인 피드백이 아니라면 코멘트 하지 않는다.
    PATAL Please Talk Another Look.
    다시 한 번 봐주세요. 리뷰가 중간에 Stop 된 경우 재요청할 때 사용.
    SGTM (Sounds Good To Me) Sounds Good To Me.
    코드 리뷰를 승인할 때 리뷰어가 사용
    IMHO / IMO In My (Humble) Opinion.
    의견 남길 때 사용.
    BTW By The Way.
    의견 남길 때 사용.

     

    1-3. 코드 리뷰 절차

    - IT 기업에서는 최대한 자유로운 절차, 방법, 리뷰 요청 양식으로 코드 리뷰를 수행한다.

    - 국내에서 가장 많이 사용하는 Git/GitHub 를 기반으로 한 코드 리뷰 절차이다. (출처: Weniv)

    코드 리뷰 절차 (출처: Weniv)

     

    2. 개발자 문화와  코드 리뷰 (Code Review)

    표절에 대한 디자이너와 프로그래머의 차이 (출처: okky.kr/articles/1252655)

    프로그래머들은 회사 소유의 코드가 아니라면 자신의 코드에 대한 재사용성을 너그럽게 인정하는 편이다.

    이런 특징은 국내 기업 10곳 중 7곳이 오픈 소스 소프트웨어를 사용하는 SW 시장의 흐름에서 비롯된게 아닌가 싶다.

    소스코드 사용, 수정, 배포를 허용하는 오픈소스 특성에 맞게 내부 소스코드를 공개하는 기업이 급격히 늘고 있다.

     

    이런 개발자들이 큰 비중을 차지하는 IT  기업들의 대표적인 문화 중 하나로 코드 리뷰 (Code Review) 가 있다.

    코드 리뷰는 IT 기업의 제품의 안정성과 품질 강화를 위한 대책으로 코드 Merge 전에 실행하는 필수 프로세스 되었다.

    대부분의 국내 IT 기업은 GitHub에서 제공하는 PR(Pull Request)를 활용해 리뷰 요청과 검토를 완료(Approve) 하는 방식으로 코드 리뷰를 진행한다.

     

    코드 리뷰 트렌드는 단순히 소프트웨어 검수 절차와 품질 테스트라기보다는 개발자와 회사가 함께 성장할 수 있도록 하는 수단으로 여겨진다.  코드 리뷰는 전체적인 구조의 일관성과 코드 스타일 유지, 기술적 지식과 노하우를 공유, 맥락(Context)과 히스토리(Histroy) 를 누가 보아도 인지할 수 있도록 개발되고 설명되어지는 지를 코드 리뷰 과정에서 살펴본다.

     

    - 코드 리뷰에 생소한 사람 : 나의 작업물을 누군가에게 평가 받는다는 느낌에 거부감을 가진다.

    - 코드 리뷰에 익숙한 사람 : 서로 다른 관점에서 코드의 안정성을 검토하여 코드의 신뢰성이 더욱 커지는 과정으로 인식

    3. 코드 리뷰는 효율적인가?

     

    Release 에 따른 개발 생산성

    대기업, 스타트업 가리지 않고 코드 리뷰가 필수라고 하지만 이게 효율적인 방법일까?

    남기에 맞춰 개발할 시간도 부족한데 코드 리뷰를 하면서 

     

    뚜렷한 아키텍처 없이 구현된 시스템은 진흙탕 속에서 구르는 난장판(mess) 과 같다. (참고자료5)

    제품 릴리즈가 거듭될 수록 개발자들은 새로운 기능 구현이 어려워지고, mess 를 관리하는데 소비된다.

     

    일단 사용화를 하고 나중에 깔끔하게 고치겠다는 생각은 자만심이다. 이러한 자만심을 가진 개발자들은 종국에 클린 코드 모드로 전환하지 못하고, mess 가 만들어지고, 생산성은 떨어지며 비용 증가를 초래한다.

     

    "소프트웨어를 유지보수하는 조직에서 코드 한 줄을 변경한다고 했을 때, 코드리뷰가 도입되기 전에는 그러한 변경의 55% 정도가 문제를 일으켰다. 그러나 리뷰 과정이 도입된 이후에는 그러한 변경의 2% 정도에서만 문제가 발생했다. " - Code Complete 저자 스티브 멕코넬 (Steve McConnell)

     

    당장은 messy 한 코드를 작성하더라도 코드 리뷰 없이 제품을 완성하는 것이 개발 속도가 빨라 보일지 모른다.

    하지만 장기적인 관점에서는 코드 리뷰 없는 개발은 진행할 수록 mess 는 커지고 개발 속도는 느려진다.

    코드 리뷰를 통해 '버그의 조기 발견', '개발 표준(convention) 준수', '중복 코드 방지', '모듈의 재사용성 증대' 등을 통해서 장기적인 관점에서 제품을 더 빠르고, 안정적으로 개발할 수 있다.

    4. 코드 리뷰 작성 양식과 리뷰 항목

     코드 리뷰는 다른 사람이 코드를 읽어야 하므로 어떤 목적에서 작업된 코드인지를 빠르게 파악할 수 있어야 한다.

    최대한 자유롭게 하되 기업 또는 팀마다 최소한의 양식을 포함한다.

    이번에 진행하고 있는 국비지원 과정인 멋쟁이사자처럼 푸트캠프을 포함하여다양한 코드 리뷰 방식을 정리해 본다.

     

    4-1. 멋쟁이사자처럼 프론트엔드 스쿨 (부트캠프)

    - PR 이 아닌 전체 압축 파일로 전송

    - 리뷰의 수정 결정 권한은 저자(코드 작성자)에게 있으며, 의견을 반영하지 않아도 됨

    - 저자가 고생하여 리뷰어가 덜 고생하게 한다. (최대한 상세하게 코멘트)

        > 전체 개요, 변경 내역, 아쉬웠던 부분, 자신이 없는 부분, 테스트 방법

    - 코드 리뷰 원하는 곳 (3곳 ~ 10곳을 CODE_REVIEW.md 파일을 압축파일에 담아 전달, Github 일 경우도 동일)

    - 코드 리뷰 요청 양식

    # 1번 리뷰
    * 범위 : abc/a.js 파일 65 ~ 72 line
    * 전체 개요
    * 기능 내용 (함수나 변수 등은 주석)
    * 기타 (잘 모르는 부분 등)
    # 2번 리뷰
    # (신규) 3번 리뷰

    -고수준에서 저수준 : 우선순위가 높은 순서대로 리뷰 진행

    - 코드 리뷰할 때 중점적으로 보는 것

    # 동작이 되는 가 (기한 내 80~90 점짜리 프로그램을 완성할 것)
    # 마틴이 클린코드에서 주장한 원칙 5개 (Solid)
        SRP: 단일 책임 원칙. 각 클래스/모듈/오브젝트는 하나의 정보만을 가질 것
        OCP:  개방 폐쇠 원칙. 확장에는 열려 있으며 변경에는 닫혀 있다.
        LSP: 리스코프 치환 원칙. 인터페이스의 서브 타이핑은 인터페이스에 정의된 형태를 최대한 유지
        ISP: 인터페이스 분리 원칙. 인터페이스는 최소한으로 유지
        DIP: 의존 관계 역전 원칙. 상위 레벨의 모듈/인터페이스가 서브 클래스나 타이핑에 영향을 받아서는 안된다.
    # 개발 원칙
        KISS : 최대한 단순함을 유지하라
        DRY : 똑같은 기능, 코드를 반복하지 마라
        YAGNI : 그 기능이 필요하기 전까지는 미리 만들지 마라
    # 클린소프트웨어
       경직성 : 프로그램 변경이 어렵다.
       취약성 : 프로그램 변경시 연관 없는 부분에서 장애 발생
       부동성 : 재사용할 수 있는 컴포넌트 구분이 어려움
       점착성 : 비효율적인 개발 환경
       불필요한 복잡성 : 직접 효용이 없는 구조가 디자인에 포함
       불필요한 반복 : 단일 추상 개념으로 통합할 수 있는 반복구조 포함
       불투명성 : 직접 만든 사람이 아닌 다른 사람이 읽고 이해하기 어려운 코드
    # 폴더 트리 (재사용할 수 있는 것은 폴더로 모을 것)
    # 반복 (재사용 가능한 코드는 컴포넌트화 할 것)
    # API (컴포넌트 안에 넣지 말고 파일로 만들어 재사용)
    # 가독성과 유지보수
    # 예외처리

    - 부트캠프 스터디 모임에서 정한 컨벤션

     (1) Commit 작성 규칙

    - Commit 할 때 '[이슈 번호] 구현 내용' 으로 작성


    예시) [#1] 로그인 모달 및 기능 구현

    (2) Pull request 작성 규칙

    # Title : Pull request 제목은 '이슈 이름 - 본인 이름'
    예시) 구현 과제 - 로그인/회원가입 모달 - 철수
    # Description : 수정사항을 입력 (markdown)


    예시) 로그인/회원가입 첫 번째 레이아웃을 구현했습니다.
    # To do
    - [x] 체크박스 기능 구현
    - [x] 로그인/비밀번호 input border 수정

    (3) 리뷰어 규칙

    1. 서로의 코드를 보고 피드백을 줄 수 있는 부분이 있다면 코멘트를 통해 리뷰를 남긴다.
        * 다른 사람이 내 코드에 대해서 지적을 했다고 기분 나빠하지 않는다. (공격이 아님)

    2. 내가 알고 있는 지식이어도 상대방의 코드를 보고 질문할 만한게 있다면 코멘트로 물어봐 준다.
    3. 구현을 잘하여고 피드백 줄게 없다면, 그냥 넘어가지 않고 칭찬을 남겨준다.

     

    4-2. 토스랩

    - 코드 리뷰 요청 양식 (토스랩 안드로이드 개발 팀)

    - Title
    Feature/Bug-fix 건인지 명시
    - 간략한 목적
    연결된 이슈
    Description
    추가/수정된 로직 항목
    어떻게 추가/수정했는지
    예시)
    Title - [fix] 소켓 API 버전 처리 (JND-3986) Description
        1. @version 커스텀 어노테이션 추가
        2. version 없는 Event 에 Version 필드 추가, @version 어노테이션 부여
        3. SocketObject -> EventObject 로 파싱하는 로직 공통 메소드로 분리
        4. 파싱 후 바로 반환하지 않고 Version Valid 로직 추가
    * Java Reflection 사용

    - 토스랩 코드리뷰 프로세스

    * 월 ~ 수: Feature/Bug-fix 개발이 업무의 최우선 순위
    * 목 ~ 금: 코드 리뷰가 업무의 최우선 순위이며 코드 리뷰 대상은 목요일 출근 전까지 리뷰 요청을 한 건을 대상으로 한다.

    - 토스랩 코드 리뷰 항목

    * 성능 개선 개발 : 시간 복잡도
    * 신규 Feature 개발 : 잠재적인 오류에 대한 검출
    * 리팩토링 : 테스트 코드나 구조에 대한 물음
    * 신규 기술 도입 : 해당 기술의 로직과 그에 대한 물음
    * 기타 : 변수명과 같은 코드 컨벤션. 전체적인 흐름을 이해하기 위해 실제 빌드를 해서 동작을 시켜보고 이해.

    - 토스랩 코드리뷰 코멘트

     (1) 가급적 상대방을 공격하지 않는 느낌을 주도록 작성

     (2) 단순히 문제를 이슈업하기 보다는 대안을 제시

     (3) 문제의 검출과 해결에 주안을 두고 진행하는 태도

    * OO 보다는 XX 가 더 나은 것 같아요.
    * OO 는 XX 부분을 참고해서 이용하면 돼요.
    * OO 는 XX 에 의해서 문제되지 않을까요?
    * XX 를 하려다가 OO 로 했는데 어떻게 생각하세요?

     

    4-3. 우아한 형제들

    - 리뷰 문화 개선 전: 리뷰가 쌓여가고 늦어지는 경우가 빈번하여 Reviewee/Reviewer 모두 고통을 받았다.

    - 코드 리뷰는 중요하지만 때로는 여러가지 이유로 소홀해질 때가 있다.

    - MR 을 생성했는데 리뷰어는 묵묵부답이고 직접 요청하자니 업무를 방해하는 건 아닌지 걱정하는 상황

    - MR : Merge Request (Pull Request)

    - 리뷰 문화 개선 포인트 : 리뷰어가 적은 노력으로 코드 리뷰를 잘할 수 있는 환경을 구성

    - 개선항목 1. 코드 리뷰 요청 양식 (우아한 형제들 공통시스템개발팀)

    # 해결하려는 문제가 무엇인가요?
    어떻게 해결했나요?
    Attachement
      (1) 이번 MR 의 Front 동작을 이해를 돕는 GIF 파일 첨부!
      (2) 리뷰어의 이해를 돕기 위한 모듈/클래스 설계에 대한 Diagram 포함!
    예시)
    # 해결하려는 문제가 무엇인가요?
    * TS2305: Module '"react-router"' has no exportd member 'useHistroy' 에러를 내면서 빌드가 깨집니다.
       다른 모듈에 의해 react-router 버전이 5 -> 6 으로 올라간 게 문제입니다.

    # 어떻게 해결했나요?
    * 사용하는 react-router 의 버전을 package.json 에 명시합니다.

    - 개선항목 2. 매일 아침 9시 30분 MR 목록 알람 (Python-gitlab 이용)

    MR 목록 슬랙 알람 (출처: https://techblog.woowahan.com/7152/)

    - 개선항목 3. 칸반 리뷰 Max : 리뷰 Max 초과 시 모든 팀원이 업무를 중단하고 리뷰 진행
    - 코드 리뷰 규칙 문서 추가 (MR 크기, 코멘트 방식)

    # 리뷰이 규칙
    * 코드 리뷰의 설명은 최대한 자세하게 작성
    * 작은 MR 을 유지 (최대 300줄 미만) : 작게 쪼개서 리뷰는 자주, 짧은 세션으로 진행
    * 코드 리뷰 우선순위를 라벨로 표기 (D-n 규칙) : 긴근한 사항은 D-0, 라벨 없으면 D-5
      (python-gitlab 을 활용해 라벨을 하루씩 감소하도록 D-n 라벨 자동화 함)

    * 최소 한 명의 리뷰어에게 리뷰를 받는다.
    * 피드백을 반영하면 코멘트를 남긴다.

    #리뷰어 규칙
    * 코드 리뷰의 코멘트를 강조하고 싶은 정도를 표기 (Pn 규칙)
       P1: 꼭 반영해 주세요 (Request Changes)
       P2: 적극적으로 고려해 주세요 (Request Changes)
       P3: 웹만하면 반영해 주세요 (Commet)
       P4: 반영해도 좋고 넘어가도 좋습니다 (Approve)
       P5: 그냥 사소한 의견입니다 (Approve)
    * 리뷰 작성자를 칭찬하기 : 코드 리뷰에 별달리 코멘트할 내용이 없다면 칭찬 코멘트 남기기

    - Pre-commit 포맷팅 자동화 : Ktlinteslintgit hook pre-commit 을 이용해 커밋하기 전 실행.
                                                              또한 MR 생성시 CI 에서 lint 를 실행하여 컨벤션을 지켰는지 추가로 확인함
                                                               * CI (Continuous Integration ) : 지속적인 통합

    - Bash Shell 로 작성된 pre-commit 자동화를 위한 스크립트

    #!/bin/sh
    
    CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '{ print $2 }')"
    CHANGED_KOTLIN_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $2 ~ /\.kts|\.kt/ { print $2}')"
    
    git_add_diff_files() {
        for file in $CHANGED_FILES; do
            git add "${file}"
        done
    }
    
    # ktlint
    ./gradlew --quiet ktlintFormat -PinternalKtlintGitFilter="$CHANGED_KOTLIN_FILES"
    
    if ! ./gradlew --continue ktlintCheck -PinternalKtlintGitFilter="$CHANGED_KOTLIN_FILES"; then
        exit 1
    fi
    
    # eslint
    cd {front directory}
    yarn run lint-staged
    cd {root directory}
    
    # 린트 실행 후 변경된 파일 다시 git add
    git_add_diff_files

    5. 방법론

    5-1. 코드 리뷰는 신속히 이뤄져야 한다.

    - 코드 리뷰의 진행 속도는 개발 생산성에 중대한 영향을 미친다.
    - 리뷰를 빠르게 해주지 않은 팀원은 자기 일은 끝냈겠지만, 코드 변경사항은 반영되지 못하고 리뷰를 기다림에 따라

          새로운 기능 추가나 버그 수정은 며칠, 몇 주 또는 몇달 씩 지연된다. (팀 전체의 속도가 감소)

    - Google 은 코드 리뷰가 요청되고 나서 영업일 기준 하루 이내에 반드시 코드 리뷰가 완료하는 것을 원칙으로 함

    - Google 코드 리뷰는 작은 변경은 1시간 이내, 규모가 큰 변경사항은 5시간 이내 검토가 완료됨

    - 다음 날 아침 최우선 업무로 처리하는 것으로 규정함 (즉시 처리 또는 관련 일정을 피드백)

    5-2. 변경 요구사항(CL)을 여러 개의 CL 로 분할한 다음 적절한 순서로 리뷰한다.

    5-3. 고수준에서 저순으로 우선순위를 잡아 리뷰한다.

    - 고수준: 버그, 장애, 성능, 보안, Extract Method (특정 패터 함수로 모으기), 복잡도 등

    - 저수준: 변수명 고치기, 파일 분리, 응집도

    5-4. 코드 수준에 맞는 코멘트를 통해 한 두 등급을 올리는 것을 목표로 한다.

    5-5. 코드 리뷰의 코멘트를 명확히 이해하고 전달할 수 있도록 이모지(Emoji) 를 활용한다.

    - 이모지를 통해서 텍스트만으로 전달하면서 발생할 수 있는 감정적인 싸움이나 소모를 예방하고 상대방을 배려한다.

    Microsoft 에서 코드 리뷰 시 사용하는 이모지 (출처: samsungsds.com)

    5-6. 최소 1명의 피드백도 진행되지 않은 코드는 통합하지 않는다.

    5-7. (태도) 코드를 비판할 때에는 코드를 비판하는 것이지 개인을 공격하는 점이 아니라는 것을 꼭 기억한다.

     - 공격적인 태도로 저자를 공격거나 방어적인 태도로만 리뷰어의 의견을 듣는 것을 방지하기 위한 문화가 필요함

    5-8. 기술적인 사실과 데이터는 의견과 개인의 선호보다 우선시되어야 한다.

    5-9. 코드 리뷰 승인까지 소요된 전체 시간이 아니라 개별 응답 시간이 코드 리뷰의 속도에 중요하다.

     - 전체 코드 리뷰 과정이 오래 걸리더라도 리뷰어의 응답이 빠르면 저자가 '느린' 코드 리뷰로 느끼는 불만은 줄어듬
     - 만약 너무 바빠서 리뷰하기 어렵다면, 언제쯤 리뷰를 시작할 것인지 응답

     - 또는 다른 리뷰어를 제안하거 포괄적인 의견을 제공한다.

    5-10. 만약 코드 작성과 같은 집중력이 필요한 작업을 하고 있다면, 코드 리뷰를 위해 일을 멈추지 않는다.

     - 리뷰를 위해 자신의 코드 작성을 멈추는 것이 다른 개발에게 리뷰를 기다리게 하는 것보다 팀에게 큰 비용이 든다.

     - 현재 코드 작업이 끝났거나, 점심식사 후, 회의가 끝난 후, 또는 휴게공간을 다녀온 후에 리뷰를 한다.

    5-11. 코드 리뷰 피라미드 (5단계 필수 질문 항목)

    - Code Style : 개발 원칙이나 개발/코드 컨벤션관련 리뷰 항목 

    - Tests : 테스트 패스 여부와 고려해야할 테스팅 관련 리뷰 항목

    - Documentation : README, API 문서, 사용자 가이드 등 문서화 리뷰 항목

    - Implementation : 성능, 복잡도, 예외처리, 보안 등의 기능 구현상 리뷰 항목

    - API Semantics : API 관련된 리뷰 항목

    - 코드 리뷰 피라미드 라이센스는 GeekNews 에서 확인 가능하며 xguru 님께서 번역함

    - 코드 리뷰 피라미드 원본은 링크를 참조

    코드 리뷰 피라미드 (출처: https://news.hada.io)

    6.  사용하는 도구

    6-1. Google

    Google 은 분석 도구를 통해 사용하지 않는 코드는 없애고, 리팩토링(Refactoring)이 필요한 경우에는 수정된 코드를 개발자들에게 리뷰 요청하는 것으로 알려져 있다.

    Tricorder : 정적 분석(잠재결함 분석 도구)

    Rosie: 코드 정리 시스템(Large-Scale and Code Changes)

     

    6-2. Microsoft

    Codeflow : 자동화된 코드 리뷰 도구

    VSCode 코드 리뷰 플러그인

    GitHub

    Visual Studiio Teams Service

     

    6-3. Kakao

    - 코드 리뷰 이전에 Unit Test 와 정적 분석을 통해 문제점을 도출 후 코드 리뷰를 진행한다.

    자체개발 TestBot : Code Smell 과 Bug, Vulnerability 등의 문제를 분석 및 알림

    자체개발 Chrome Extension : 코드 리뷰어 자동 할당

    Discourse : 온라인 코드 리뷰 토론과 정보 공유

    오프라인 리뷰 : 중요한 모듈의 코드 리뷰

     

    6-4. 우아한 형제들

    - Gitlab

    - Slack

     

     

     

    참고자료1. https://www.samsungsds.com/kr/insights/global_code_review.html 

    참고자료2. https://www.theteams.kr/teams/859/post/64467

    참고자료3. https://techblog.woowahan.com/7152/

    참고자료4. https://madplay.github.io/post/speed-of-code-reviews

    참고자료5. https://brunch.co.kr/@cleancode/34

    참고자료6. https://techwell.wooritech.com/blog/2021/04/19/%EC%BD%94%EB%93%9C%EB%A6%AC%EB%B7%B0/

Designed by Tistory.