Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Book] 켄트 벡의 Tidy First? - 더 나은 소프트웨어 설계를 위한 32가지 코드 정리법

Kent Beck이 오랜만에 새 책을 출간했다는 것은 작년 말에 ak님의 트윗을 보고 알게 되었다. 주니어 시절 Kent Beck의 책이나 글에서 영향을 많이 받았기에 오랜만에 그의 책이 반가웠다.

코드 작성도 쉽지 않던 시절 당시 TDD를 전파하려고 노력하던 분들이 있어서 Kent Beck을 알게 되었고 Kent Beck이 비행기에서 Gang of Four(GoF) 중 한 명인 Erich Gamma를 만나서 JUnit을 만들었다거나 2001년 애자일 소프트웨어 개발 선언을 서명한 사람 중 한 명이라는 것은 나에겐 영웅담처럼 느껴졌다.

Kent Beck의 테스트 주도 개발 (Test Driven Development: By Example)에서 큰 감명을 받기도 했고 익스트림 프로그래밍 - 변화를 포용하라, 2판에서도 Kent Beck의 통찰력이나 실용적인 접근에 감탄하곤 했다. 다음은 XP를 읽고 내가 당시 느꼈던 소감이다.

최근 제가 관심 가지는 분야 때문에 그런 건지 모르겠지만 TDD부터 해서 많은 부분에서 켄트벡이라는 이름을 접하게 됩니다. 개인적으로는 한 명의 개발자가 전 세계적으로 개발이라는 분야에 이토록 큰 영향을 줄 수 있다는 것에 감탄하곤 합니다. TDD 책에 이어 이 책에서도 켄트벡이라는 사람이 가진 생각과 사상에 감탄하게 되더군요. 이 책을 왜 이제야 읽었나 싶습니다.

책을 읽으면서 느낀 것은 보통 실천 방법이 많이 알려져 있는데 실제로 켄트벡이 강조하려고 하는 것은 이 실천 방법보다는 가치 쪽에 있다는 생각이 듭니다.(물론 켄트 벤 말 대로 너무 추상적이긴 하지만요) 그렇다고 가치만 죽~ 설명하면 말이 안 되니까 그걸 지킬 수 있게 실천 방법을 연구해서 강조한것 인데 켄트 백의 의도와 다르게 우리는 이 실천 방법에 더 집중하고 있는 듯 합니다.(마음에 와닿는 문서안만들기 같은...)

2009년에 Kent Beck이 한국에 와서 당시 회사에서는 휴가 쓰기도 쉽지 않았는데 사비로 Kent Beck 초청 세미나(#1, #2)에 설레며 갔다 왔던 기억이 나고 이후에 잘 안되는 TDD도 익숙해 보려고 연습도 하곤 했다.

반응적 설계(Responsive Design)은 책으로 나올 예정인데 "Structured Design"이란 책은 보고 많은 영감을 받았고 여기서 기본적인 내용을 잡았다고 합니다. 이 책의 저자들과 얘기해서 기본적인 것을 바탕으로 내용을 업데이트하고 켄트 백의 생각을 넣어서 새로 만들려고 하는 것이 Responsive Design의 시작이라고 합니다.

설계는 모든 완벽한 정보에 기반해서 하지 않기 때문에 실수에 대한 비용도 있습니다. 또한 현재 하는 일 때문에 하지 못하는 것에 대한 기회비용도 있습니다. 프로젝트 초기에는 해당 프로젝트의 가능성을 잘 모르기 때문에 기회비용이 클 수 있는데 초기 투자를 줄여서 프로젝트가 진행되면서 확실해졌을 때 더 많이 투자함으로써 기회비용을 줄일 수 있습니다. 반응적 설계는 효율은 높이고 비용은 줄이는 것입니다.

Agile이나 TDD, XP 등 Kent Beck에게 받은 다양한 영감을 제 개발 커리어에 많은 영향을 주었다고 생각한다. 물론 Kent Beck 한 명에게만 영향받은 건 아니고 그가 알려준 대로 지속해서 계속 개선하진 못했지만 지금 와서 많은 접근 방법에 Kent Beck이 얘기하던 부분이 많이 녹아있다는 생각이 들었다. 물론 잊어버린 것이 더 많을 거라는 생각한다.

그래서 오랜만에 나온 Kent Beck의 책이 꽤 반가웠고 예전 느낌이 살아나는 것 같았다.

Tidy First?

"코드 정리가 먼저인가?"로 이해할 수 있는 이 책의 질문은 코드 정리에 대해 다양한 팁을 전하면 고민거리를 많이 던져주는 책이다.

안전하지 않은 행동을 하면서 불안해하는 것은 경솔하게 안전을 확신하며 우둔하게 있는 것보다 훨씬 낫습니다.
사람들이 안전하게 설계하는 법을 배우도록 돕는 것이 제 사명입니다. 따라서 책 전체에서 작고 안전한 단계로 작업하는 예를 자주 보게 될 것입니다. 저는 단기적으로 급히 해결하는 일에는 관심이 없습니다. 소프트웨어 설계는 가치를 만들고, 그렇게 만들어진 가치는 시간이 흐른 뒤에야 현실에 나타납니다.

이 책은 완전히 새로운 얘기를 한다기보다 20년 가까이 Kent Beck이 고민하고 전파하던 생각을 다시 한번 간결하게 정리했다는 게 더 어울릴 듯하다. 이 책의 도입부에서도 나오는데 이 책을 읽고 예전에 Kent Beck 책 후기를 찾아보다가 15년 전에도 Edward YourdonLary Constantine이 쓴 Structured Design에 영감을 받아서 반응형 설계를 시작하게 되었다는 것을 알게 되었다.

'이 책에서 읽은 어떤 말도 믿지 마세요'('Don't believe anything you read in this book!') 라는 에드워드 요던의 글귀와 함께 그의 사인 그리고 '위 내용도 포함'('...including the above!')이라는 글귀와 래리 콘스탄틴의 사인

저는 더 많은 사람이 소프트웨어 설계를 잘 다루고 소중하게 여기기를 바랍니다.

경험적인 소프트웨어 설계는 "언제?"라는 질문에 대한 해답을 제시합니다. "설계를 활용할 수 있는 시기를 설계하세요" 이 답을 하려면 취향, 협상, 판단력이 필요합니다. 취향과 판단력이 필요하다는 것이 약점일까요? 물론입니다. 하지만, 피할 수 없는 약점입니다. 추측형이나 반응형 설계 모두 판단력이 필요하지만, 이때는 소프트웨어 설계자가 사용할 수 있는 도구가 적습니다.

이 책은 다른 책과 달리 140 페이지 정도의 얇은 책으로 누구나 책을 보자마자 너무 얇아서 금방 읽겠는데? 하는 생각을 할 것이다. 책의 내용도 좋지만, 그 내용을 얼마나 간결하게 전달할 수 있을지에 대한 고민을 끊임없이 하면서 반복하는 Kent Beck의 접근 방식에는 배울 것이 많다고 생각한다.

제 책 쓰기 경험은 항상 이런 식이었습니다. 일단 책 한 권으로 쓰기에는 너무 작아 보이는 주제를 잡습니다. 그리고 씁니다. 책으로 쓰기에는 너무 큰 주제임을 깨닫습니다. 너무 작다 싶은 조각으로 또 나눕니다. 다시 씁니다. 아직도 너무 큰 것임을 발견합니다. 이 과정을 반복합니다.

어떤 면에서는 자신의 모든 행동에 자신의 원칙이 지켜지도록 노력하는 Bret Victor의 Inventing on Principle 발표가 생각나기도 했다.

Part 1 코드 정리법

이 책은 Kent Beck이 3권으로 기획한 시리즈의 첫 번째 책으로 코드 관리에 대한 개인적인 접근에 대해서 다루고 있다. 아마 다음 책에서는 협업에 대한 부분을 다루게 될 것 같다.

저는 이 코드 정리법을 좋아하는데, 매우 간단하기 때문입니다. 이것은 이 책 "켄트 벡의 Tidy First?"을 구성하는 철학의 일부입니다. 소프트웨어 설계가 큰일이 되면, 설계 작업을 아예 그만두고 싶은 위험에 직면합니다. 그러나 적절한 소프트웨어 설계는 변화를 가능하게 합니다. 작은 소프트웨어 설계로 변화를 좀 더 쉽게 만들 수 있습니다.

코드를 만드는 데, 가장 큰 비용이 들어가는 일은 코드 작성이 아니라 읽고 이해하는 데 드는 비용입니다. 코드 정리를 선행하면 더 작은 조각 단위로 결합을 제거하는 길을 제시하여 응집도를 높일 수 있습니다.

코드를 읽다가 '아, 이건 이렇게 돌아가는 거구나!'라는 생각이 드는 순간을 아시죠? 바로 그 순간이 소중한 순간입니다. 기록하세요.
코드에서 명확하지 않은 내용만 골라 적으세요. 자신이 이 코드를 처음 읽는 사람이라고 가정해 보세요. 아니면 15분 전의 자신을 떠올려 보세요.

코드만으로 내용을 모두 이해할 수 있다면 주석은 삭제하세요.

2부 관리

항상 Kent Beck에게 느끼듯이 리팩토링을 일상처럼 만들어서 작업하는 중에 일상생활처럼 조금씩 코드를 정리 습관을 들일 수 있도록 한다는 느낌을 받았다.

단계의 크기는 본인이 알아서 하겠지만, 제가 권하는 것은 아주 작은 단계로 나누어 코드를 정리하는 방식을 고수하면서 실험해 보는 것입니다. 그러면서 각 단계를 최적화하세요.

코드 구조를 대대적으로 바꾸려고 코드 정리를 시작하는 경우가 많습니다. 너무 많이, 너무 빠르게 변경하지 않도록 주의하세요. 대개 작은 정리를 순차적으로 성공하는 것이 무리한 정리로 실패하는 것보다 시간을 아껴줍니다.

소프트웨어 설계에 대한 한 가지 척도를 제시합니다. 바로 개인에게 영향을 미치는 소프트웨어 설계죠. 그래서 분 단위를 유지하되, 한 시간을 넘지 않습니다. 한 번의 코드 정리에 한 시간 이상이 걸린다면, 이는 원하는 동작 변경을 위해 필요한 최소한의 구조 변경 시기를 놓쳤다는 의미일 수 있습니다.

소프트웨어 설계라고 하면 평소 꽤 큰일처럼 느껴지고 리팩토링이라는 것도 "오늘은 리팩토링해야지"라거나 스프린트 후 기술 부채 갚는 시간을 가지는 등 꽤 크고 무겁게 접근하는 경향이 있는데 Kent Beck은 이러한 작업을 작게 쪼개고 반복해서 정리하면서 지속해서 개선하는 것을 권장합니다.

소프트웨어 설계는 '길을 닦는' 일의 성격이 매우 강하기 때문입니다.

세 가지 선택이 있지만, 그 중 어느 것도 매력적이지 않습니다.
* 그대로 배포할 수 있습니다. 검토하는 사람들이 무례하다 느끼고, 오류가 발생하기 쉽지만 당장 처리할 수 있습니다.
* 코드 정리와 변경 사항을 별도의 하나 이상의 PR로 나누거나 하나의 PR을 여러 번의 커밋으로 나눌 수 있습니다. 이 방법으로 무례함은 줄일 수 있지만 작업 횟수는 늘어납니다.
* 진행 중인 작업을 버리고 코드 정리를 선행하는 순서로 다시 시작할 수 있습니다. 이렇게 하면 작업은 더 많아지지만, 이어지는 커밋과의 일관성은 분명해집니다.

변경 사항을 추가하면서 코드 정리까지 같이 해버리는 경우가 많은데 이러한 상황에서 나는 두 번째 방법을 보통 선택하는 편이다. 공들여서 Git으로 hunk를 하나하나 커밋에 추가해서 논리적 단위로 정리는 정리대로 변경 사항은 변경 사항대로 PR을 만들어서 올리는 편이다. 예전에는 작업하고 나서 일일이 정리했다면 시간이 지나면서는 작업하다가 논리적 단위가 분리되어 생각될 때 그때 바로 PR을 분리해서 올리고 또 이어서 작업하는 편이다.

익숙해져서 딱히 불편하게 느끼지도 않았지만, 3번째 방법은 생각해 보지 않았다. 모든 걸 이해한 상태에서 새로 작업한다는 건 꽤 흥미로운 접근 방법이긴 하다.

우리가 작성하는 코드는 컴퓨터에 지시할 뿐 아니라, 컴퓨터에 지시하려는 여러분의 의도를 사람들에게 설명해야 하기 때문이죠. 컴퓨터에 지시만 하는 빠른 수행이 흥미로운 최종 목표가 되는 것은 아닙니다.
이쯤 되면 세 번째 선택지를 실험해 보라고 권하는 것이 그리 놀랍지 않을 것입니다. 다시 구현하면서 새로운 것을 발견할 가능성이 높아집니다.

제가 나중에 코드를 정리하겠다는 선택을 마치 정답처럼 여기는 이유는 지금 정리할 코드가 너무 많으니 언제 해도 상관없다는 논리입니다.

다음의 경우라면 동작 변경 후 코드 정리하세요.
* 방금 고친 코드를 다시 변경할 예정일 때
* 지금 정리하는 것이 더 저렴할 때
* 코드 정리하는 데 드는 시간이 동작 변경에 드는 시간과 거의 비슷할 때

일반적으로 코드를 먼저 정리하는 것을 선호하지만, 정리 그 자체를 목적으로 삼지 않도록 경계해야 합니다.

3부 이론

'소프트웨어 설계의 의미'에 대해 '요소들을 유익하게 관계 맺는 일'이라고 말할 수 있습니다.

소프트웨어는 두 가지 방식으로 가치를 만듭니다.
* 현재 소프트웨어가 하는 일
* 미래에 새로운 일을 시킬 수 있는 가능성

3부에서는 코드 정리의 가치를 금융과 비교해서 설명한다. 코드 정리를 먼저 할지 말지, 정리한다면 어느 정도를 할지에 대한 고민을 할 때 언제나 트레이드 오프가 있을 수밖에 없는데, 그에 대한 기준을 고민할 수 있는 근거를 알려준다고 느껴졌다.

젊은 엔지니어 시절에 저는 안정된 듯 보였던 상황이 혼란스럽게 변할 때 두려움을 느꼈습니다. 하지만 옵션을 통해 선택 가능성을 늘리는 방법을 배우고 나서는 혼란이 기회가 되었습니다.

선택 가능성을 유지하고 확장하기 위해 구조에 투자해야 한다는 것을 알고 있다고 해도, 실제로 투자했는지 여부는 알 수 없습니다.

사람들은 동작 변경과는 다르게 구조 변경에 대해서는 혼란스러워합니다. 이 책은 이러한 질문에 대한 답을 대신 제시하는 것이 아니라 여러분이 스스로 답을 찾을 수 있도록 도와줍니다. 구조 변경과 동작 변경은 모두 가치를 만들어 재미나, 근본적으로 다르다는 것을 이해하는 것부터 시작하세요. 어떻게 다를까요? 한마디로 말하면 되돌릴 수 있는 능력 즉, 가역성입니다.

최고의 교훈은 바로 이것입니다. 가치에 대한 예측이 불확실할수록 바로 구현하는 것보다 옵션이 지닌 가치가 더 커집니다. 바로 구현하는 것보다 변화를 포용하면 제가 창출한 가치를 극대화할 수 있는데, 그 상황은 통상적으로 소프트웨어 개발이 가장 극적으로 실패하는 바로 그 지점입니다.

금융과 비교한 옵션의 가치에서 코드 정리를 하면 더 많은 동작 변경이 가능해져서 옵션, 즉, 선택할 수 있는 폭이 넓어지기 때문에 이를 기준으로 정리의 시점과 양을 판단할 수 있다.

소프트웨어 설계를 옵션의 관점에서 생각해 보니 제 생각이 완전히 뒤집혔습니다. 옵션을 만드는 것과 동작을 변경하는 것의 균형을 맞추는 데 집중하자, 예전에는 두렵기만 했던 일이 이제는 설레는 일이 되었습니다.
* 잠재적인 동작 변경의 가치가 변동성이 클수록 더 좋습니다.
* 개발 기간이 길면 길수록 좋습니다.
* 물론 앞으로 더 저렴하게 개발할 수 있다면 더 좋겠지만, 그것은 가치의 극히 일부분에 불과했습니다.
* 더 작은 설계 작으므로 옵션을 만들 수 있다면 더 좋았습니다.

좀 더 명확하게 판단할 수 있는 기준도 제시하고 있다.

자, 확실히 코드 정리부터 해야 할 때가 있습니다. 언제일까요?
비용(코드 정리) + 비용(코드 정리 후 동작 변경) < 비용(바로 동작 변경)

이보다 곤란한 상황은 다음과 같은 경우에 발생합니다.
비용(코드 정리) + 비용(코드 정리 후 동작 변경) > 비용(바로 동작 변경)

몇 분에서 몇 시간에 이르는 코드 정리 규모에서 우리는 코드 정리의 경제성을 정확하게 계산할 수 없으며, 계산하려고 시도해서도 안 됩니다. 우리는 두 가지 중요한 형태의 판단력을 길러서, 나중에 더 큰 일을 실행하려고 합니다.
* 소프트웨어 설계와 시기와 범위에 영향을 미치는 인센티브를 인식하는 데 익숙해지기("설계에 더 많은 시간을 할애하고 싶은데 반발이 너무 심해요. 무슨 일일까요?")
* 대인 관계 기술을 우리 자신에게 연습해서, 나중에 밀접하게 일하는 동료부터 더 넓은 범위의 동료에게까지 활용하기

일반적으로 되돌릴 수 있는 결정은 되돌릴 수 없는 결정과 다르게 취급해야 합니다.

우리가 지금 무엇을 하려는지 아시겠나요? '서비스로 추출하기(extract as a service)'를 적어도 당분간은 되돌릴 수 있게 만들고 있습니다.

또한 경제성을 판단하려면 비용의 기준이 있어야 하기 때문에 그에 대한 기준도 제시합니다.

결합도 분석은 단순히 프로그램의 소스 코드를 보는 것만으로는 부족합니다. 두 요소가 결합되어 있는지 여부를 판단하려면, 먼저 어떤 변경이 발생했거나 발생할 가능성이 있는지 알아야 합니다.

결합도와 응집도에 관한 최초의 연구인 "Structured Design"(Yourdon, 1975)에서 에드워드 요던과 래리 콘스탄틴은 소프트웨어 설계의 목표는 소프트웨어의 비용을 최소화하는 것이라고 가정했습니다.

제가 '콘스탄틴의 등가성'이라고 이름 붙인 이 용어에 따르면, 소프트웨어 비용은 그것을 변경하는 데 드는 비용과 거의 같습니다.
비용(소프트웨어) ~= 비용(변경)

가장 비용이 많이 드는 하나의 변경이 나머지 변경을 모두 합친 것보다 훨씬 더 많은 비용이 듭니다. 다시 말해, 변경 비용은 큰 변경들의 비용과 거의 같다는 뜻입니다.
비용(전체 변경) ~= 비용(큰 변경들)

무엇이 변경을 '전파'할까요? 바로 결합도 입니다. 따라서 소프트웨어 비용는 결합도와 거의 같습니다.
비용(큰 변경들) ~= 결합도
이제 우리는 완전한 콘스탄틴의 등식을 얻었습니다.
비용(소프트웨어) ~= 비용(전체 변경) ~= 비용(큰 변경들) ~= 결합도
혹은 소프트웨어 설계의 중요성을 강조하기 위해 이렇게 바꿀 수 있습니다.
비용(소프트웨어) ~= 결합도
따라서 소프트웨어 비용을 줄이려면 결합도를 줄여야 합니다. 하지만 결합도를 줄이는 것은 공짜가 아니며 절충점을 피할 수 없습니다.

결합도가 왜 있는지는 사실 중요하지 않습니다. 지금 결합도 비용을 지불할 것인지, 아니면 결합도를 없애는 비용을 지불할 것인지 여러분은 선택의 기로에 서게 됩니다. "코드 정리가 먼저인가?"라는 질문에 대한 답이 바로 이 결정의 축소판입니다.

결국 소프트웨어의 비용은 결합도에 달려있으므로 코드 정리에 대한 판단도 결합도를 어떻게 할지에 달려있다고 할 수 있다. 개발하면 자주 응집도와 결합도에 대해서 얘기하지만 명쾌하게 정리해 주니 신성하고 인상적이었다.

제 믿음 중에서, 증명하거나 적절하게 설명할 수 없는 것이 하나 있습니다. 한 종류의 코드 변경에 대한 결합도를 줄일수록 다른 종류의 코드 변경에 대한 결합도가 커진다는 것입니다. 이것이 의미하는 실질적인 의미는 (여러분의 직관과 일치한다면) 모든 결합을 다 색출하듯 없애려고 애쓰지 말아야 한다는 것입니다. 그렇게 만들어진 결합도는 그만한 가치가 없습니다.

이제 책의 제목이기도 한 "코드 정리가 먼저인가?"라는 질문에 답할 준비가 되셨나요? 그래도 다시 한번 반복합니다. 매전 조금씩 다르지만, 매번 다음 네 가지 힘에 의해 영향을 받습니다.
* 비용: 코드를 정리하면 비용일 줄까요? 아니면 나중에 하는 편이 나을까요? 아니면, 줄일 가능성이 있을까요?
* 수익: 코드를 정리하면 수익이 더 커질까요? 혹은 더 빨리 발생하거나 커질 가능성이 있나요?
* 결합도: 코드를 정리하면 변경에 필요한 요소의 수가 줄어드나요?
* 응집도: 코드를 정리하면 변경을 더 작고 좁은 범위로 집중시켜 더 적은 수의 요소만 다룰 수 있을까요?

코드 정리에 너무 집착하지 마세요.

일반적으로 코드를 다룰 때 기능 변경에 대해서라면 자신이 옳다고 생각하는 일을 해도 사람들이 불만을 가질 수 있는 위험과 불확실성을 지닙니다. 반면에 코드 정리에 영향을 받을 사람은 바로 나 자신이기 때문에 만족할 가능성이 매우 높습니다.

한 가지 코드 정리를 하고 나면 다음 정리를 하고 싶은 충동이 생기겠지만, 억제해야 합니다. 또한, 코드 정리는 다음 동작 변경을 가능하게 합니다. 그러나 변경을 기다리는 다른 누군가가 기다리다 폭발하지 않도록 코드 정리를 나중에 해야할 때도 있습니다.

코드 정리를 먼저 하실 건가요? 아마도요. 바로 그것입니다. 예, 여러분을 위해서 충분히 그만한 가치가 있습니다.

이 책을 보면 하라는 거야? 말라는 거야? 하면서 이랬다저랬다 하는 느낌이 들 수도 있는데 그런 부분이 Kent Beck의 매력이지 않나 싶기도 하다. 얇은 책이었지만 많은 생각을 해볼 수 있는 재미난 책이었다. 최근엔 좋은 내용이고 무슨 말인지 알겠는데 이렇게 길게 설명할 필요가 있나 싶은 책도 많았는데 Kent Beck스럽게 실용적으로 책을 썼다는 생각이 들었다.

옮긴이 노트

국내에 번역된 Tidy First?에는 원서에는 없는 옮긴이 노트라는 별책부록을 제공한다.

책을 읽으면서 번역도 깔끔해서 쉽게 이해하면서 읽었는데 옮긴이 노트에는 번역을 하면서 Kent Beck의 의도를 잘 설명하려고 Kent Beck과 얘기를 주고받은 과정이 나와 있는데 책만큼이나 이 부분이 재밌었다. 책을 쓰는 게 보통 힘든 일이 아니고 각 역자와 얘기나누는게 쉬운 일은 아닌데 "그 질문을 주고받는 과정이 자신의 글쓰기 실력을 높이는 방법"이라고 한 부분이 특히 인상적이었다. 모든 질문에 최선을 다해 자신의 의도가 한국어로도 잘 전달되도록 노력하고 있다는 생각이 들었다. 물론 Kent Beck뿐 아니라 안영회님도 포함이다.

옮긴이 노트의 뒷부분은 안영회님의 20년간에 소프트웨어 설계에 대한 고민과 경험이 에세이처럼 담겨있다. 분량으로 보면 Kent Beck 정도의 분량으로 책을 쓰신게 아닐까 싶다. 종종 온라인에서 글을 보긴 했지만, 또 이렇게 책으로 보니 재미있었다.

처음 Kent Beck이 책을 썼다는 걸 알게 되고 책이 얇아서 원서로 읽을까 하다가 번역이 시작되었다는 걸 알고 번역서가 나오길 기다리고 있었다.(사실 읽을 책이 없는 것도 아니고...) 번역서가 잘 나온 덕에 재밌게 다양한 고민을 하면서 잘 읽었다.

2024/05/14 22:14 2024/05/14 22:14