How to Do Code Reviews Like a Human (Part Two)

Michael Lynch

사람처럼 코드 리뷰하는 법 (2부)

이 글은 코드 리뷰에서 효과적으로 소통하고 함정을 피하는 방법에 대한 글의 후반부입니다. 여기서는 추한 갈등을 피하면서 코드 리뷰를 성공적으로 마무리하는 기법에 집중합니다.

기초가 되는 내용은 1부에서 다뤘으니 먼저 그 글을 읽어보시길 권합니다. 급하신 분들을 위해 요약하자면, 좋은 코드 리뷰어는 단순히 버그를 찾는 데 그치지 않고 동료가 성장할 수 있도록 성실한 피드백을 제공합니다.

내 생애 최악의 코드 리뷰

내 생애 최악의 코드 리뷰는 Mallory라고 부를 전 동료와의 리뷰였습니다. 그녀는 제가 합류하기 몇 년 전부터 회사에 다녔지만, 최근에 제 팀으로 옮겨온 참이었습니다.

리뷰

Mallory가 처음으로 리뷰를 위해 체인지리스트를 보냈을 때 코드는 다소 거칠었습니다. 그녀는 Python을 한 번도 써본 적이 없었고, 제가 유지보수하던 낡고 다루기 어려운 레거시 시스템 위에서 작업을 하고 있었습니다.

저는 발견한 문제를 하나하나 성실하게 적어 내려갔고, 총 59개였습니다. 제가 읽었던 리뷰 관련 글들에 따르면 저는 훌륭한 일을 해낸 것이었습니다. 그렇게나 많은 실수를 찾아냈으니, 당연히 훌륭한 리뷰어임에 틀림없었습니다.

며칠 뒤 Mallory는 수정된 체인지리스트와 제 코멘트에 대한 답변을 보내왔습니다. 오타나 변수명 변경 같은 간단한 문제는 고쳐져 있었습니다. 하지만 잘못된 입력에 대해 정의되지 않은 동작을 한다거나, 함수 하나에서 제어 흐름이 여섯 단계나 중첩되어 있다는 식의 더 근본적인 문제들은 고치기를 거부했습니다. 대신 그런 문제를 고치는 데 공수를 들일 가치가 없다는 식으로 퉁명스럽게 설명했습니다.

화가 나고 답답해진 저는 다시 코멘트를 남겼습니다. 겉으로는 전문적인 말투였지만 수동 공격적인 영역을 헤매고 있었습니다. “잘못된 입력에 대해 정의되지 않은 동작을 원하시는 이유를 설명해 주시겠어요?” 짐작하시겠지만, Mallory의 답변은 한층 더 완고해졌습니다.

코드 리뷰라는 바위를 이리저리 밀어내는 모습

씁쓸한 악순환

일주일 뒤 화요일이었습니다. Mallory와 저는 여전히 같은 리뷰를 두고 공방을 벌이고 있었습니다. 전날 저녁에 저는 최신 코멘트를 보내놓은 상태였습니다. 그녀가 퇴근할 때까지 일부러 보내지 않고 기다렸습니다. 그녀가 제 글을 읽는 순간 같은 공간에 있고 싶지 않았기 때문입니다.

아침 내내 다음 라운드의 리뷰가 두려워 속이 무겁게 가라앉는 기분이었습니다. 점심을 먹고 돌아오니 Mallory는 자리에 없었지만 새로운 변경 사항을 보내놓은 상태였습니다. 아마 그녀도 제가 답장을 읽는 모습을 보고 싶지 않았던 모양입니다.

그녀의 답변을 하나씩 읽을수록 화가 치밀어 가슴이 쿵쾅거렸습니다. 저는 곧바로 키보드를 두드리며 반박 글을 쓰기 시작했습니다. 제가 제안한 수정을 하지도, 제가 승인할 만한 근거를 제시하지도 않았다고 지적하면서 말입니다.

우리는 이런 일을 매일같이 3주 동안 반복했습니다. 코드는 거의 바뀌지 않았습니다.

중재

다행히 가장 시니어인 팀원 Bob이 이 악순환을 끊어주었습니다. 긴 휴가를 마치고 돌아온 Bob은 우리가 서로에게 날카로운 리뷰 코멘트를 던지고 있는 모습을 보고 깜짝 놀랐습니다. 그는 즉시 상황이 교착 상태에 빠졌음을 알아차렸습니다. 그가 리뷰를 넘겨받겠다고 하자 우리는 둘 다 동의했습니다.

Bob은 리뷰를 시작하면서 Mallory에게 체인지리스트를 새로 만들도록 부탁했습니다. 우리가 그다지 다투지 않았던 두 개의 작은 라이브러리를 각각 30~50줄 규모로 분리해 달라는 것이었습니다. Mallory가 그렇게 하자 Bob은 즉시 승인했습니다.

이어서 Bob은 200줄 정도로 줄어든 메인 체인지리스트로 돌아왔습니다. 몇 가지 가벼운 제안을 남겼고, Mallory는 이를 반영했습니다. 그러자 Bob은 체인지리스트를 승인했습니다.

Bob의 리뷰 전체는 이틀 만에 끝났습니다.

소통이 중요하다

짐작하셨겠지만 이 갈등은 사실 코드 자체에 관한 것이 아니었습니다. 코드에 분명 정당한 문제는 있었지만, 소통이 원활한 동료들이라면 충분히 해결할 수 있는 수준이었습니다.

불쾌한 경험이었지만 돌이켜보면 겪길 잘했다고 생각합니다. 덕분에 리뷰에 대한 제 접근법을 재점검하고 개선할 점을 찾을 수 있었습니다.

아래에서는 비슷한 불쾌한 결과를 겪을 위험을 줄여줄 기법들을 공유합니다. 뒤에서 Mallory 이야기로 다시 돌아와 제 원래 접근법이 왜 잘못되었고 Bob의 접근법이 왜 조용히 빛났는지 설명하겠습니다.

기법들

  1. 코드를 한두 등급 올리는 것을 목표로 하라
  2. 반복되는 패턴에 대한 피드백은 제한하라
  3. 리뷰 범위를 존중하라
  4. 큰 리뷰를 나눌 기회를 찾아라
  5. 진심 어린 칭찬을 하라
  6. 남은 수정이 사소하면 승인하라
  7. 교착 상태에 선제적으로 대처하라

코드를 한두 등급 올리는 것을 목표로 하라

이론적으로 동료는 코드를 개선할 모든 기회를 탐색하고 싶을지 모르지만, 인내심에는 한계가 있습니다. 체인지리스트를 다듬을 새롭고 훌륭한 방법을 계속 떠올리며 라운드마다 승인을 미룬다면 동료는 금방 지쳐버릴 것입니다.

저는 혼자 속으로 코드를 A부터 F까지 학점으로 평가합니다. D 수준에서 시작하는 체인지리스트를 받으면, 작성자가 이를 C나 B-까지 끌어올리도록 돕는 것을 목표로 합니다. 완벽하지는 않지만 충분히 괜찮은 수준입니다.

이론적으로는 D를 A+까지 끌어올릴 수도 있지만, 아마 여덟 차례 이상의 리뷰가 필요할 것입니다. 그 과정이 끝나면 작성자는 당신을 싫어하게 되고 다시는 코드를 보내고 싶어 하지 않게 될 것입니다.

리뷰어가 작성자의 글을 한 등급 올려주도록 돕는 모습

“C 수준의 코드를 받아들이면 결국 코드베이스 전체가 C 수준이 되는 것 아닌가?”라고 생각하실 수도 있습니다. 다행히 그렇지 않습니다. 제 경험상 동료가 D에서 C로 올라서도록 도우면, 다음에 보내오는 체인지리스트는 처음부터 C에서 시작합니다. 몇 달이 지나면 B에서 시작하는 리뷰를 보내오고, 리뷰가 끝날 때쯤에는 A가 됩니다.

F는 기능적으로 틀렸거나 너무 복잡해서 정확성을 확신할 수 없는 코드에만 해당합니다. 몇 차례 리뷰를 거친 뒤에도 코드가 여전히 F 수준에 머물러 있을 때만 승인을 보류해야 합니다. 아래 교착 상태에 관한 절을 참고하시기 바랍니다.

반복되는 패턴에 대한 피드백은 제한하라

작성자의 실수 여러 개가 같은 패턴에 속한다는 것을 알게 되었다면, 모든 사례를 하나하나 지적하지 마십시오. 같은 코멘트를 25번 쓰는 데 시간을 쓰고 싶지 않을 것이고, 작성자 역시 중복된 코멘트 25개를 읽고 싶어 하지 않습니다.

한 패턴에 대해 두세 곳 정도를 짚어주는 것은 괜찮습니다. 그 이상이라면 개별 사례마다 지적하기보다 패턴 자체를 고쳐달라고 요청하십시오.

반복되는 패턴을 지적하는 예시

리뷰 범위를 존중하라

제가 자주 보는 안티 패턴 중 하나는 리뷰어가 체인지리스트에 포함된 코드 근처에 있는 무언가를 찾아 작성자에게 고쳐달라고 하는 경우입니다. 작성자가 이에 응하면 리뷰어는 코드가 좋아지긴 했지만 일관성이 떨어지므로 몇 가지 사소한 수정을 더 해야 한다는 것을 깨닫습니다. 그리고 또 몇 가지를 더 요구하게 됩니다. 그렇게 좁은 범위로 시작한 체인지리스트가 관련 없는 변경으로 가득 차게 될 때까지 계속됩니다.

『If You Give a Mouse a Cookie』 표지

배고픈 생쥐 한 마리가 현관 앞에 나타나면 쿠키를 주고 싶어질지도 모릅니다. 쿠키를 주면 이번엔 우유 한 잔을 달라고 할 겁니다. 우유 거품이 입가에 묻지 않았는지 거울을 보고 싶어 할 것이고, 그러다 머리를 다듬겠다며 가위를 달라고 할 겁니다…

-로라 조피 뉴머로프(Laura Joffe Numeroff), If You Give a Mouse a Cookie

경험칙은 간단합니다. 체인지리스트가 건드리지 않은 줄은 리뷰 범위를 벗어난 것입니다.

예를 들어보겠습니다.

범위를 벗어난 코드 줄 예시

코드베이스에 남아 있는 매직 넘버와 터무니없는 변수 이름 때문에 밤잠을 설치게 되더라도, 범위를 벗어난 것은 벗어난 것입니다. 근처 코드를 작성한 사람이 바로 이번 작성자라 해도 마찬가지입니다. 정말 심각하게 나쁘다면 버그를 등록하거나 직접 수정안을 제출하십시오. 하지만 이번 리뷰에서 작성자에게 떠넘기지는 마십시오.

예외는 체인지리스트가 실제로 해당 줄을 건드리지는 않았지만 주변 코드에 영향을 미치는 경우입니다. 예를 들면 다음과 같습니다.

범위에 포함되는 코드 줄 예시

이 경우 작성자가 함수명을 ValidateAndSerialize에서 Serialize로 바꿔야 한다고 지적하십시오. 함수 시그니처가 있는 줄을 직접 건드리지는 않았지만, 결과적으로 그 줄이 잘못된 상태가 되었기 때문입니다.

코멘트가 많지 않은데 범위 바로 밖에 쉬운 수정 거리가 보인다면 저는 이 규칙을 부드럽게 어기기도 합니다. 이럴 때는 작성자가 원하면 코멘트를 무시해도 된다는 점을 분명히 밝힙니다.

범위를 벗어난 문제를 지적하는 예시

큰 리뷰를 나눌 기회를 찾아라

400줄이 넘는 체인지리스트를 받았다면 작성자에게 더 작은 단위로 나눠달라고 요청하십시오. 제한을 많이 초과할수록 더 강하게 나누기를 요구하십시오. 저는 개인적으로 1,000줄이 넘는 체인지리스트는 리뷰 자체를 거부합니다.

마술사가 큰 리뷰를 나누는 모습

작성자는 체인지리스트를 나누는 일이 귀찮기 때문에 불만을 표할 수도 있습니다. 논리적인 경계를 찾아주면 부담을 덜어줄 수 있습니다. 가장 쉬운 경우는 체인지리스트가 여러 파일을 서로 독립적으로 건드리는 경우입니다. 이때는 파일을 묶어 더 작은 체인지리스트로 나누면 됩니다. 더 어려운 경우에는 추상화 수준이 가장 낮은 함수나 클래스를 찾으십시오. 작성자에게 이 부분을 별도의 체인지리스트로 옮긴 뒤, 첫 번째 체인지리스트가 머지되면 나머지 코드로 돌아오자고 요청하십시오.

코드 품질이 낮을 때는 더욱 단호하게 분리를 요청하십시오. 품질이 낮은 코드를 리뷰하는 난이도는 크기에 따라 기하급수적으로 증가합니다. 600줄짜리 엉망인 체인지리스트 하나를 검토하는 것보다 허술한 300줄짜리 체인지리스트 두 개를 보는 편이 훨씬 낫습니다.

진심 어린 칭찬을 하라

대부분의 리뷰어는 무엇이 잘못되었는지에만 집중하지만, 리뷰는 긍정적인 행동을 강화할 수 있는 소중한 기회입니다.

예를 들어 문서 작성에 어려움을 겪는 작성자의 코드를 리뷰하다가 명확하고 간결한 함수 주석을 발견했다고 상상해 보십시오. 잘했다고 알려주십시오. 실수했을 때만 지적하기보다 잘했을 때 알려주면 훨씬 더 빠르게 성장합니다.

MMA 경기에서 진심 어린 칭찬을 하는 모습

칭찬을 위해 특별한 목표를 세울 필요는 없습니다. 체인지리스트에서 마음에 드는 부분을 발견할 때마다 저는 작성자에게 이렇게 알려줍니다.

  • “이 API는 몰랐는데 정말 유용하네요!”
  • “우아한 해결책이네요. 저는 생각도 못 했을 겁니다.”
  • “이 함수를 나눈 건 정말 좋은 아이디어였습니다. 훨씬 단순해졌네요.”

작성자가 주니어 개발자이거나 팀에 새로 합류한 경우라면 리뷰 중에 긴장하거나 방어적인 태도를 보이기 쉽습니다. 진심 어린 칭찬은 당신이 가혹한 문지기가 아니라 든든한 동료임을 보여줌으로써 긴장을 풀어줍니다.

남은 수정이 사소하면 승인하라

일부 리뷰어는 마지막 코멘트 하나까지 수정된 것을 직접 확인할 때까지 승인을 보류해야 한다고 잘못 생각합니다. 이는 불필요한 리뷰 라운드를 만들 뿐이며 작성자와 리뷰어 모두의 시간을 낭비합니다.

다음 중 하나라도 해당하면 승인하십시오.

  • 더 이상 남길 코멘트가 없을 때.
  • 남은 코멘트가 사소한 문제에 관한 것일 때.
    • 예: 변수명 변경, 오타 수정
  • 남은 코멘트가 선택적인 제안일 때.
    • 동료가 승인이 제안 반영에 달려 있다고 오해하지 않도록 선택 사항임을 명시하십시오.

코드 주석 끝에 마침표를 빠뜨렸다는 이유로 승인을 보류하는 리뷰어를 본 적이 있습니다. 제발 그러지 마십시오. 그런 행동은 작성자에게 감독 없이는 간단한 문장 부호조차 제대로 못 찍는 사람으로 본다는 신호를 줍니다.

아직 처리되지 않은 코멘트가 있는 상태에서 승인하는 데는 약간의 위험이 따릅니다. 제 경험상 약 5% 정도의 경우 작성자가 마지막 라운드 코멘트를 오해하거나 아예 놓치기도 합니다. 이를 방지하기 위해 저는 승인 후 작성자의 변경 사항을 확인합니다. 드물게 오해가 생기면 작성자에게 다시 연락하거나 제가 직접 수정한 체인지리스트를 만듭니다. 5%의 경우에 약간의 추가 작업이 생기는 것이 나머지 95%에 불필요한 노력과 지연을 더하는 것보다 낫습니다.

교착 상태에 선제적으로 대처하라

코드 리뷰에서 일어날 수 있는 최악의 결과는 교착 상태입니다. 즉, 추가 수정 없이는 승인할 수 없다는 리뷰어와, 그 수정을 거부하는 작성자가 맞서는 상황입니다.

교착 상태로 향하고 있다는 신호는 다음과 같습니다.

  • 논의 분위기가 점점 긴장되거나 적대적으로 변한다.
  • 라운드당 코멘트 수가 줄어드는 추세를 보이지 않는다.
  • 비정상적으로 많은 수의 코멘트에 대해 반발이 돌아온다.

코드 리뷰 중 팽팽한 긴장감

직접 대화하라

직접 만나거나 화상 통화를 하십시오. 텍스트로만 소통하다 보면 대화 상대가 실제 사람이라는 사실을 잊기 쉽습니다. 동료가 고집이나 무능력 때문에 그런다고 쉽게 단정하게 됩니다. 한 번의 만남이 그런 착각을 깨뜨려 줄 것입니다. 당신에게도, 작성자에게도 말입니다.

설계 리뷰를 고려하라

논쟁이 격한 코드 리뷰는 더 이른 단계의 프로세스에 허점이 있음을 시사할 수 있습니다. 설계 리뷰에서 다뤘어야 할 문제를 두고 다투고 있는 것은 아닌가요? 애초에 설계 리뷰가 있었나요?

의견 충돌의 근원이 상위 수준의 설계 결정으로 거슬러 올라간다면, 코드 리뷰에 참여한 두 사람에게만 맡길 것이 아니라 더 넓은 팀이 의견을 내야 합니다. 설계 리뷰 형태로 논의를 팀 전체로 확장하는 것에 대해 작성자와 대화해 보십시오.

양보하거나 상부에 요청하라

동료와 교착 상태에 빠진 채 시간이 길어질수록 관계에 입는 피해는 커집니다. 다른 방법으로도 교착을 풀지 못했다면 선택지는 양보하거나 상부에 판단을 요청하는 것뿐입니다.

그냥 승인했을 때의 비용을 따져보십시오. 낮은 품질의 코드를 무심코 받아들이면서 양질의 소프트웨어를 만들 수는 없습니다. 하지만 동료와 너무 격하게 싸워 더 이상 함께 일할 수 없게 된다면 높은 품질 역시 달성할 수 없습니다. 체인지리스트를 승인한다면 실제로 얼마나 나빠질까요? 중요한 데이터를 파괴할 수도 있는 코드인가요? 아니면 최악의 경우 작업이 실패하고 개발자가 디버깅하면 되는 백그라운드 프로세스인가요? 후자에 가깝다면 동료와 좋은 관계를 유지하기 위해 그냥 양보하는 것을 고려해 보십시오.

양보할 수 없다면 논의를 팀 매니저나 테크 리드에게 상신하는 것에 대해 작성자와 이야기하십시오. 다른 리뷰어에게 재지정하겠다고 제안하는 것도 방법입니다. 상신 결과가 자신에게 불리하게 나오더라도 결정을 받아들이고 넘어가십시오. 계속 다투면 상황만 길어지고 전문적이지 못한 인상을 남기게 됩니다.

교착 상태에서 회복하기

지저분한 리뷰 논쟁은 코드 자체보다는 관련된 사람들 사이의 관계에 관한 경우가 많습니다. 교착 상태나 그에 준하는 상황에 이르렀다면 근본적인 갈등을 해결하지 않는 한 같은 패턴이 반복될 것입니다.

  • 매니저와 상황을 논의하라.
    • 팀에 갈등이 있다면 매니저는 이를 알아야 합니다. 어쩌면 작성자가 유독 다루기 어려운 사람일 수도 있습니다. 혹은 당신이 인지하지 못하는 방식으로 상황에 기여하고 있을지도 모릅니다. 좋은 매니저라면 두 사람 모두가 문제를 해결하도록 도와줄 것입니다.
  • 서로에게서 잠시 거리를 두라.
    • 가능하다면 상황이 식을 때까지 몇 주간 서로에게 코드 리뷰를 보내지 마십시오.
  • 갈등 해결을 공부하라.
    • 저는 Crucial Conversations라는 책이 도움이 되었습니다. 조언이 상식처럼 들릴 수도 있지만, 논쟁의 열기에서 벗어나 자신의 갈등 대처 방식을 분석하는 데는 큰 가치가 있습니다.

내 최악의 코드 리뷰: 다시 돌아보기

Mallory와의 코드 리뷰를 기억하시나요? 왜 제 리뷰는 3주 동안 수동 공격적인 진흙탕 싸움이 되었는데, Bob의 리뷰는 이틀 만에 가볍게 끝났을까요?

내가 잘못한 점

이 리뷰는 Mallory가 팀에서 받은 첫 리뷰였습니다. 저는 그녀가 평가받는다고 느끼거나 방어적이 될 수 있다는 점을 고려하지 못했습니다. 처음에는 높은 수준의 코멘트만 남겨서 방대한 양의 코멘트에 기습당했다고 느끼지 않도록 했어야 했습니다.

제 역할이 그녀의 일을 방해하는 것이 아니라 앞으로 나아가도록 돕는 것임을 보여주기 위해 더 노력했어야 했습니다. 코드 예시를 제공하거나 긍정적인 부분을 짚어주는 일을 할 수도 있었습니다.

자존심이 리뷰에 영향을 미치도록 내버려 두었습니다. 지난 1년간 저는 이 낡은 시스템을 살려내기 위해 애써왔습니다. 그런데 갑자기 새로운 사람이 와서 이 시스템을 건드리면서 제 우려를 진지하게 받아들이려 하지 않는 것이었습니다. 저는 이를 모욕으로 받아들였지만, 그런 태도는 오히려 역효과를 냈습니다. 제가 모든 리뷰에서 유지하려 노력하는 객관적인 마음가짐을 지켰어야 했습니다.

마지막으로 교착 상태가 너무 오래 지속되도록 방치했습니다. 몇 차례를 거친 뒤에도 의미 있는 진전이 없다는 것이 분명했어야 합니다. 과감한 변화를 시도했어야 했습니다. 예를 들어 직접 만나 더 깊은 갈등을 다루거나 매니저에게 상신하는 식으로 말입니다.

Bob이 잘한 점

리뷰를 나눈 Bob의 첫 조치는 매우 효과적이었습니다. 3주 동안 고통스럽게 정체되어 있던 리뷰였는데, 갑자기 두 조각의 코드가 머지되었습니다. 이는 앞으로 나아가고 있다는 추진력을 만들었기에 Mallory와 Bob 모두에게 좋은 기분을 안겨주었습니다. 남은 부분에는 여전히 문제가 있었지만, 더 작고 관리하기 쉬운 체인지리스트가 되었습니다.

Bob은 리뷰를 완벽할 때까지 조이려 하지 않았습니다. 아마도 제가 그토록 목소리 높이던 문제들을 그도 알아보았겠지만, Mallory가 앞으로 오랫동안 팀에 함께할 사람임을 깨달은 것입니다. 단기적인 유연성이 장기적으로 Mallory의 품질을 개선하도록 도울 수 있는 위치를 만들어 주었습니다.

결론

이 글의 전반부를 발행한 뒤, 제가 추천한 소통 방식에 대해 문제를 제기하는 독자들이 여럿 있었습니다. 어떤 분들은 그것이 잘난 체하는 것처럼 느껴진다고 했고, 다른 분들은 너무 간접적이어서 오해의 소지가 있다고 우려했습니다.

그 피드백은 합리적이고 예상 가능한 것입니다. 같은 간결한 리뷰 코멘트라도 어떤 사람에게는 무뚝뚝하거나 무례하게 느껴질 수 있고, 다른 사람에게는 간결하고 효율적이라고 느껴질 수 있습니다.

코드 리뷰에서는 무엇을 집중할지, 피드백을 어떻게 표현할지, 언제 승인할지 등 많은 선택을 하게 됩니다. 중요한 것은 제 선택을 따르는 것이 아니라 선택지가 존재한다는 사실을 인식하는 것입니다.

완벽한 리뷰를 위한 정해진 레시피를 줄 수 있는 사람은 아무도 없습니다. 가장 효과적인 기법은 코드 작성자의 성향, 그와의 관계, 팀 문화에 따라 달라집니다. 코드 리뷰의 결과를 비판적으로 사고하며 자신만의 방식을 다듬어 나가십시오. 긴장이 발생하면 한 걸음 물러서서 왜 그런 일이 일어났는지 평가해 보십시오. 리뷰의 질에 주의를 기울이십시오. 코드 품질을 원하는 기준까지 끌어올릴 수 없다고 느낀다면, 리뷰 과정의 어떤 측면이 방해가 되고 있는지, 그리고 그것을 어떻게 해결할 수 있을지 고민해 보십시오.

행운을 빕니다. 여러분의 코드 리뷰가 인간답기를 바랍니다.

더 읽어보기


이 글은 Samantha Mason이 편집했습니다. 삽화는 Loraine Yow가 담당했습니다. @global4g 님께 초안에 귀중한 피드백을 제공해 주셔서 감사드립니다.

원문은 Michael Lynch님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.