The WET Codebase(번역)

created:
last-updated:

The WET Codebase
Screenshot 2025-02-09 at 10.52.27 PM.png

안녕하세요. 저는 물을 많이 마시는 법을 배웠습니다. 안녕하세요, 제 이름은 댄 아브라모프입니다. 저는 React라는 JavaScript 라이브러리를 개발하고 있습니다. 사실, 이번 컨퍼런스는 제가 React 또는 JavaScript와 관련되지 않은 행사에서 발표하는 첫 번째 자리입니다. 그래서 궁금한데요, 혹시 React를 사용해 보신 분 계신가요? 오, 네. 많은 분들이 React를 사용하시네요. 멋집니다. 하지만 오늘 발표는 React에 대한 이야기가 아닙니다. 이 발표는, 만약 제가 타임머신을 타고 과거의 저 자신에게 돌아갈 수 있다면 꼭 해주고 싶은 이야기입니다. 한마디로, 깊고 깊은 바닷속 어딘가에 있는 아주 먼 코드베이스에 관한 이야기입니다.

Screenshot 2025-02-09 at 10.54.36 PM.png

제가 오래전에 작업했던 코드베이스가 있습니다. 그 코드베이스에는 두 개의 서로 다른 모듈, 즉 두 개의 파일이 있었는데요, 제 동료이자 친구가 그 파일 중 하나에서 새로운 기능을 개발하고 있었습니다. 그러다가 그 기능과 매우 유사한 기능이 이미 다른 파일에 구현되어 있다는 사실을 발견했죠. 그래서 그는 이렇게 생각했습니다. "어? 이미 비슷한 기능이 있네? 그냥 그 코드를 복사해서 붙여넣으면 되겠는데?"

그리고 나서 저에게 코드 리뷰를 요청했습니다. 그때 저는 막 ‘최고의 개발 실천법’에 대한 책들을 잔뜩 읽고 난 상태였습니다. 《실용주의 프로그래머》, 《클린 코더》, 《깔끔한 코드》 같은 책들이요. 그래서 저는 알고 있었죠. "코드를 복사해서 붙여넣으면 유지보수 부담이 커지고 다루기 어려워진다." 게다가 저는 ‘DRY’라는 개념도 배운 참이었습니다. DRY는 "Don't Repeat Yourself(자신을 반복하지 마라)"의 약자죠. 그래서 저는 이렇게 말했습니다. "이거 코드 복붙(복사 & 붙여넣기) 같은데요? DRY 원칙을 좀 적용해서 개선할 수 있을까요?"

그러자 제 동료는 이렇게 말했습니다. "아, 네. 물론이죠. 그 코드를 별도의 모듈로 추출해서 두 파일이 모두 그 모듈을 참조하도록 하면 되겠네요." 그렇게 해서 추상화가 탄생했습니다. 여기서 ‘추상화’란 어떤 특정한 프로그래밍 언어와 상관없이, 함수, 클래스, 모듈, 패키지 등과 같이 코드베이스에서 여러 곳에서 재사용할 수 있는 요소를 의미합니다.

네 정말 멋져 보이죠. 그리고 그들은 행복하게 살고 있습니다. 이제 그 추상화가 어떻게 발전했는지 살펴봅시다. 그 다음에는 한동안 그 코드를 보지 않았는데 새로운 기능을 개발 중이었고 실제로 매우 유사한 것이 필요했습니다. 원래의 추상화는 비동기식이었지만 동기식이라는 점을 제외하면 거의 똑같은 형태가 필요했습니다.

그래서 그 코드를 직접 재사용할 수는 없었지만 약간 다른 점을 제외하면 거의 똑같은 코드이기 때문에 복사해서 붙여넣는 것도 정말 하기 싫었습니다. 그래서 이 두 부분을 통합하고 추상화를 좀 더 멋지게 만들어서 케이스도 처리할 수 있도록 하자고 생각했습니다. 그리고 우리는 그것에 대해 정말 기분이 좋았습니다. 약간은 비정통적이지만 코드와 실제 생활이 만나면 이런 일이 벌어지잖아요? 약간의 타협을 하면 적어도 코드를 복제할 필요는 없으니 좋지 않겠죠?

Screenshot 2025-02-09 at 10.58.57 PM.png

그래서 그 다음에는 이 새로운 코드, 새로운 기능에 버그가 있다는 사실을 알게 되었고, 그 버그는 우리가 가지고 있는 코드와 똑같은 코드가 필요하다고 생각했기 때문에 발생했습니다. 하지만 실제로는 약간 다른 것이 필요했습니다. 물론 특수 케이스를 추가하면 이 버그를 수정할 수 있습니다. 따라서 추상화에는 if 문을 사용할 수 있습니다. 이 특정 경우와 같다면 약간 다른 작업을 수행하세요. 좋아, ship it 그렇게 하자. 모든 추상화에는 그런 일이 일어나니까요, 그렇죠?

추상화에서 구체적인 사용 사례에 해당하는 부분을 꺼내 보겠습니다. 이렇게 생겼습니다. 이제 우리의 추상화는 구체적인 사례에 대해 알지 못합니다. 매우 일반적이고 매우 아름답습니다. 더 이상 아무도 그것이 무엇을 나타내는지 이해하지 못합니다. 덧붙여서, 이제 다른 곳에서 매개변수화되었으므로 모든 코드 크기가 매개변수화되었는지 확인해야 합니다. 그리고 결국 이렇게 되죠.

Screenshot 2025-02-09 at 11.01.58 PM.png

그리고 다시 말하지만, 각각의 개별 단계는 어느 정도 의미가 있습니다. 하지만 원래 무엇을 하려고 했는지 잊어버리면 전체 그림을 더 이상 보지 못하기 때문에 순환적 의존성이나 어딘가에서 성장하고 있는 이상한 일이 있다는 것을 알지 못합니다. 물론 현실에서는 아무도 코드 베이스의 일부를 건드리고 싶지 않아서 오랫동안 정체되어 있다가 누군가 다시 작성했기 때문에 실제로는 거기서 스토리가 끝납니다. 그리고 승진을 했을지도 모르죠. 글쎄요 모르겠어요.

하지만 현실이 아닌 이야기이기 때문에 타임머신이 있다면 과거로 돌아가서 고칠 수 있겠죠? 그래서 저는 추상화가 여전히 의미가 있었던 시점으로 돌아가고 싶습니다. 하지만 세 번째 사례가 있는데 약간 다른 코드가 필요하지만 그 코드를 복제하고 싶지 않았습니다. 그래서 그들은 '그래, 추상화에서 타협하자'고 했습니다. 재미있게 만들자고요. 제가 오늘 그 자리에 있었다면 이렇게 말했을 겁니다. '이 추상화를 인라인으로 만들어주세요.'

Screenshot 2025-02-09 at 11.05.20 PM.png

따라서 인라인이란 말 그대로 코드를 가져와서 이를 사용하는 곳에 복사하여 붙여넣기만 하면 된다는 뜻입니다. 그렇게 하면 약간의 중복이 발생하지만 우리가 만들려고 했던 잠재적인 괴물이 파괴됩니다. 물론 중복도 장기적으로 완벽하지 않지만, 잘못된 추상화 역시 장기적으로 완벽하지 않습니다. 따라서 우리는 이 두 가지 문제의 균형을 맞춰야 합니다. 그래서 이 방법이 도움이 되는 이유는 여기에 버그가 있고 실제로 이 버그가 다른 기능을 수행해야 한다는 것을 깨달으면 바로 변경할 수 있기 때문입니다. 그리고 격리되어 있기 때문에 다른 곳에는 영향을 미치지 않습니다. 마찬가지로 여기서 다른 버그가 발견되어 변경할 수도 있습니다.

Screenshot 2025-02-09 at 11.06.36 PM.png
Screenshot 2025-02-09 at 11.07.24 PM.png

항상 복사해서 붙여넣기만 하라는 것은 아닙니다. 장기적으로 보면 이러한 부분이 정말 안정화되고 의미가 있다는 것을 깨닫게 될 수도 있습니다. 그리고 무언가를 꺼냈는데 원래 좋은 추상화라고 생각했던 것이 아닐 수도 있습니다. 다른 것일 수도 있죠. 그리고 이런 일은 실제로도 마찬가지입니다. 제가 어렸을 때 이 말을 들었더라면 그런 말이 아니라고 말했을 거예요. 복사 붙여넣기는 정말 안 좋은 안티패턴이라고 들었어요.

그래서 그들은 새로운 세대에게 가르칩니다. 하지만 새로운 세대가 이러한 결론에 도달한 이유와 장단점을 이해하지 못하면 실제로 언제 이게 나쁜 생각인지, 어디까지 확장할 수 있는지 판단할 수 있는 맥락이 없습니다. 따라서 그들은 이러한 모범 사례와 안티패턴을 극단적으로 적용하려다가 스스로 문제에 부딪히게 됩니다. 그래서 그들은 다음 세대를 가르칩니다. 어쩌면 이 고리에서 벗어나지 못하고 계속 반복될 수밖에 없는 상황일 수도 있지만, 어쩌면 괜찮을 수도 있습니다.

이러한 고리를 끊기 위한 한 가지 방법은 다음 세대에게 무언가를 가르칠 때 2차원적으로 베스트프랙티스와 반대되는 안티패턴이 있다고만 말해서는 안 된다고 생각합니다. 하지만 실제로 무엇을 거래하고 있는지 설명하려고 노력해야 합니다. 이 아이디어의 이점은 무엇이고 비용은 무엇일까요? 추상화의 이점에 대해 이야기할 때 당연히 이점이 있습니다. 컴퓨터 전체가 추상화의 거대한 스택입니다. 추상화의 구체적인 장점은 특정 의도에 집중할 수 있다는 점입니다. 따라서 이걸 가지고 있는데 모든 것을 머릿속에 담아두어야 한다면 말이죠.

Screenshot 2025-02-09 at 11.12.10 PM.png

하지만 특정 레이어에 집중할 수 있다는 것은 정말 좋은 기능입니다. 이메일을 보내는 코드가 여러 곳에 있는데 이메일이 어떻게 전송되는지 알고 싶지 않다면 이메일이 어떻게 전송되는지 알 수 없습니다. 이메일이 도착하는 것조차도 저에게는 미스터리입니다. 하지만 이메일 보내기라는 함수를 호출하면 대부분의 경우 잘 작동합니다. 그리고 그것에 집중할 수 있어서 정말 좋습니다. 물론 또 다른 장점은 자신이나 다른 사람이 작성한 코드를 재사용할 수 있고 실제로 어떻게 작동하는지 기억하지 않아도 된다는 점입니다.

따라서 다른 곳에서 이미 사용하고 있는 것과 똑같은 것이 필요한 경우, 이를 재사용할 수 있다는 것은 매우 좋은 일입니다. 이것이 바로 추상화의 장점입니다. 또한 추상화는 몇 가지 버그를 방지하는 데도 도움이 됩니다. 따라서 버그가 있는 예제에서 무언가를 복사하여 붙여넣었을 수 있습니다. 복사 붙여넣기에 대한 반대 논거는 무언가를 복사 붙여넣은 후 한 버전에서 버그를 발견하고 수정했지만 다른 버전은 복사 붙여넣기를 잊어버려서 계속 고장난 상태라는 것입니다. 따라서 이는 무언가를 추출하여 제거하려는 이유에 대한 좋은 논거가 됩니다.

Screenshot 2025-02-09 at 11.14.17 PM.png

하지만 이점에 대해 이야기할 때는 비용에 대해서도 이야기해야 합니다. 이러한 비용 중 하나는 추상화가 우발적인 결합(커플링)을 일으킨다는 것입니다. 즉, 추상화를 사용하는 두 개의 모듈이 있는데 그 중 하나에 버그가 있다는 것을 알게 됩니다. 말 그대로 코드가 있는 곳이 추상화이므로 추상화에서 버그를 수정해야 합니다. 하지만 이제 이 추상화의 다른 모든 호출 사이트와 실제로 다른 곳에서 수정 사항을 도입했는지, 코드 베이스의 다른 부분에서 버그를 도입했는지 여부를 고려하는 것은 사용자의 책임입니다. 이는 하나의 비용입니다. 감당할 수 있을지도 모르죠. 우리 대부분은 그렇게 살아가고 있습니다. 하지만 이는 실제 비용입니다.

Screenshot 2025-02-09 at 11.16.16 PM.png

그리고 더 위험한 비용은 추상화로 인해 발생할 수 있는 추가적인 간접성이라고 생각합니다. 즉, 코드의 특정 레이어에만 집중할 수 있고 모든 레이어에는 신경 쓰지 않아도 된다는 약속을 한 것이죠. 정말 그렇게 될까요? 아마 대부분의 분들이 한 레이어로부터 시작했을 때 '아, 여기로 이어지는군. 그래.' 하는 경험을 하셨을 거라 생각합니다. 맞습니다. 사실은 그렇지 않습니다. 버그는 모든 레이어에 걸쳐 있기 때문에 이 레이어와 다른 레이어를 이해해야 합니다. 그리고 우리 머릿속에는 매우 제한된 스택이 있습니다.

그래서 사이트가 그런 식으로 코딩된 이유는 아마도 스택이 무너지는 것일 것입니다. 그래서 스파게티 코드를 피하려고 너무 열심히 노력하다 보니 레이어가 너무 많아서 무슨 일이 일어나고 있는지 전혀 알 수 없는 라자냐 코드를 만드는 경우가 많습니다. 이는 추가적인 간접성(인디렉션)입니다. 그리고 그들은 스스로를 고착시키지 않는다면 그렇게 나쁘지는 않을 것입니다.

따라서 추상화는 코드 베이스에 관성을 만들어내기도 합니다. 이는 기술적 요인보다 사회적 요인이 더 큽니다. 제가 여러 번 보아온 것은 정말 유망해 보이고 이해가 되는 추상화부터 시작하는 것이었습니다. 그리고 시간이 지날수록 점점 더 복잡해집니다. 하지만 특히 팀에 새로 합류한 사람이라면 이러한 추상화를 리팩터링하거나 풀 시간이 없습니다. 복사하여 붙여 넣는 것이 더 쉬울 것이라고 생각할 수도 있지만, 먼저 해당 코드에 익숙하지 않기 때문에 더 이상 그렇게 하는 방법을 모릅니다. 둘째, 최악의 사례만 제안하는 사람이 되고 싶지 않다는 것입니다. 여기에 복사 붙여넣기를 사용하자고 말하는 사람이 되고 싶은 사람은 누구일까요? 그 팀에 얼마나 오래 있을 거라고 생각하세요?

그래서 현실을 있는 그대로 받아들이고 계속 작업하면서 이 코드가 더 이상 내 책임이 아니기를 바랄 뿐입니다. 문제는 팀에서 추상화가 나쁘고 인라인을 사용해야 한다는 데 실제로 동의하더라도 이미 너무 늦을 수 있다는 것입니다. 따라서 구체적인 사용법에 익숙하고 테스트 방법만 알고 있는 경우가 발생할 수 있습니다. 추상화를 풀면 변경 사항이 아무 것도 깨뜨리지 않았는지 확인하는 방법을 이해할 수 있습니다. 하지만 여기서는 다른 팀에서 사용하고 저기서는 다른 팀에서 사용할 수도 있고, 이 팀이 개편되어 해당 코드를 유지 관리하는 팀이 없어져서 더 이상 테스트하는 방법을 모를 수도 있습니다. 따라서 변경하고 싶어도 변경할 수 없습니다.

추상화를 만들지 말라는 말이 아닙니다. 그것은 매우 2차원적이거나 1차원적인 생각일 수 있습니다. 제 말은 우리가 실수할 수 있는 일들이 있다는 것입니다. 그렇다면 어떻게 하면 실수로 인한 위험을 줄이거나 완화할 수 있을까요? 그래서 제가 React 팀에서 배운 것 중 하나는 구체적인 비즈니스 가치가 있는 코드를 테스트하는 것이었습니다. 즉, 약간 불안정한 추상화가 있지만 버그를 수정하고 새 하반기가 시작되기 전에 공백이 생겨서 제대로 된 테스트를 작성할 수 있는 시간이 생겼다고 가정해 보겠습니다.

Screenshot 2025-02-09 at 11.20.33 PM.png

따라서 해당 부분에 대한 단위 테스트 커버리지를 작성하려고 합니다. 직관적으로 유닛 테스트를 넣을 수 있는 곳은 복잡한 코드가 있는 추상화 부분입니다. 그러니 그 코드를 커버하기 위해 단위 테스트를 넣자. 왜냐하면 나중에 이 추상화가 나쁘다고 판단하고 이를 복사 붙여넣기로 바꾸려고 하면 테스트를 통해 어떤 일이 일어날지 예상할 수 있기 때문입니다. 모두 실패합니다. 이제 모든 테스트를 다시 작성하고 싶지 않으니 다시 되돌려야 할 것 같네요. 저는 코드 커버리지를 줄이자고 제안한 사람이 되고 싶지는 않아요. 그러니 그렇게 하지는 마세요.

Screenshot 2025-02-09 at 11.24.07 PM.png
Screenshot 2025-02-09 at 11.24.20 PM.png

하지만 타임머신으로 돌아가서 단위 테스트나 통합 테스트 또는 요즘 유행에 따라 부르고 싶은 테스트를 실제로 우리가 신경 쓰는 코드에 대해 이 코드가 구체적인 기능에 대해 작동하는지를 검사하도록 작성할 수 있습니다. 그리고 추상화에 신경 쓰지 않는 테스트도 있습니다. 따라서 추상화를 다시 인라인할 수 있습니다. 5개의 추상화 레이어를 만들 수 있습니다. 테스트는 이 코드가 작동하는지 여부를 알려줍니다. 따라서 실제로 리팩터링이 올바른지 알 수 있기 때문에 리팩터링하도록 안내해 줄 것입니다. 따라서 구체적인 코드를 테스트하는 것은 좋은 전략입니다.

Screenshot 2025-02-09 at 11.25.42 PM.png

또 하나는 레이어 추가를 미루는 것입니다. 이 전체 요청이 보입니다. 중복된 것 같아서 가려운 느낌이 들죠. 그리고는 '아니, 산책 좀 하자'라고 생각하게 되죠. 고등학교 때 짝사랑했던 사람이 있는데 그 사람도 내가 좋아하는 것과 같은 Last.fm의 잘 알려지지 않은 밴드를 좋아하고 있을 수도 있기 때문이죠. 그렇다고 해서 공통점이 많아서 좋은 인생의 동반자가 될 수 있다는 의미는 아닙니다. 따라서 코드에도 똑같이 적용해서는 안 됩니다. 두 스니펫의 구조가 비슷해 보인다고 하더라도 아직 문제를 제대로 이해하지 못했다는 뜻일 수도 있습니다. 그리고 이것이 우연히 비슷한 코드가 아니라 동일한 문제라는 것을 실제로 보여줄 수 있도록 시간을 좀 더 주세요.

마지막으로, 실수를 해도 괜찮다는 것이 팀 문화의 일부가 되어야 한다는 것, 이런 추상화는 나쁘다는 것이 중요하다고 생각합니다. 우리는 그것을 제거해야 합니다. 건강한 개발 프로세스의 일부로써 추상화를 추가하는 것뿐만 아니라 삭제할 수 있어야 합니다. 즉, 이런 주석을 남기고 "이봐, 이건 통제 불능이야"라고 말해도 괜찮다는 뜻입니다. 잠시 시간을 내어 복사하여 붙여넣고 나중에 이를 어떻게 처리할지 생각해 보겠습니다.

하지만 여기에는 기술적인 요소도 있습니다. 만약 종속성 트리가 다음과 같다면 인라인하고 싶은 항목이 있더라도, 복사할 수는 있겠지만 현재 복제되고 있는 공유되고 있는 뮤터블한 상태(스테이트)가 있기 때문에 실제로 인라인하기가 정말 어려울 수 있습니다. 그리고 그 모든 종속성을 어떻게 다시 연결할지 알아내야 합니다. 실현 가능하지 않을 수도 있습니다. 그래서 그냥 포기하게 되죠. 그리고 저는 이에 대한 좋은 해결책이 없습니다. 제가 발견한 것은 일부 코드의 경우 이를 피할 수 없다는 것입니다. 예를 들어 React 자체의 소스 코드에는 이런 문제가 있습니다. 사용자가 직접 수정할 필요가 없도록 저희가 대신 수정하려고 하기 때문입니다. 따라서 모듈 간에는 생각하기 다소 어려울 수 있는 상호 의존성이 존재합니다.

하지만 제 생각에 React의 멋진 점은 이와 같은 의존성 트리로 앱을 작성할 수 있다는 점입니다. 예를 들어 폼에서 사용되는 버튼 컴포넌트가 있고, 그 폼은 앱에서 사용됩니다. 이런 식으로요. 그리고 이 트리 모양을 따릅니다. 그리고 데이터 흐름이 한 방향으로만 흐르도록 제약이 있습니다. 따라서 상황이 이상하게 순환되는 것을 기대할 수 없습니다. 실수를 저지르고 잘못된 추상화를 만들 수 있지만, 기술을 통해 이를 쉽게 제거할 수 있을까요?

리액트 컴포넌트와 관리와 같은 다른 제한된 형태의 종속성에는 보통 복사해서 붙여넣기만 하면 인라인으로 연결할 수 있는 멋진 속성이 있다고 생각합니다. 따라서 잘못된 결정을 내리더라도 너무 늦기 전에 취소할 수 있습니다. 따라서 이것은 사회적 측면과 기술적 측면 모두에서 고려해야 할 사항입니다. 그러니 반복하지 마세요. DRY는 꽤 좋은 아이디어일 수 있는 원칙 중 하나일 뿐입니다.

Screenshot 2025-02-09 at 11.30.20 PM.png

개발자로서 이 업계에 입문하는 사람이라면 좋은 아이디어를 많이 들을 수 있습니다. 또는 15년 동안 이 일을 하다가 몇 달 동안 외부로 나간 사람으로서도요. 그리고 우리는 이러한 것들을 중심으로 많은 전도를 봅니다. 그리고 그것은 괜찮습니다. 하지만 이러한 것들이 무엇을 하는 것인지 또는 왜 좋은 아이디어인지 설명하려고 할 때는 항상 정확히 무엇을 거래하고 있으며, 어떤 것들이 우리를 그 원칙이나 아이디어로 이끌었는지 설명하는 것이 중요하다고 생각합니다. 그리고 그 문제의 유효기간은 어떻게 되나요? 때로는 가정된 맥락이 있고 그 맥락이 실제로 바뀌지만 그 사실을 깨닫지 못하는 경우가 있기 때문입니다. 따라서 다음 세대는 정확히 무엇이 트레이드오프인지 이해해야 합니다.

그래서 제가 여러분께 드리는 챌린지는 자신의 경험에서든, 누군가가 말해줬기 때문이든, 스스로 생각해냈기 때문이든, 여러분이 사실이라고 강하게 믿고 있는 모범 사례와 반대되는 패턴을 몇 가지 골라보고, 왜 그런 것들을 믿고 있는지, 정확히 무엇이 트레이드오프인지 세분화하여 분석해보는 것입니다. 이 강연이 흥미로웠다면 다른 강연도 마음에 드실 겁니다. 샌디 메츠의 '모든 사소한 것들'은 이러한 아이디어와 다른 많은 아이디어를 훨씬 더 자세히 설명하는 놀라운 강연입니다. 최소 API 표면적은 제 동료인 세바스찬의 강연으로 이 모든 것을 배웠습니다. 그리고 추상화의 스펙트럼은 추상화가 어떻게 힘과 표현력을 제약과 교환하는 데 도움이 되는지, 그리고 이러한 제약이 실제로 어떻게 우리를 제한할 수 있지만 그렇지 않으면 할 수 없는 일을 할 수 있게 해주는지에 대해 설명하는 Cheng Lou의 흥미로운 강연입니다. 좋은 강연이었습니다. 초대해줘서 고마워요 그게 제가 가진 전부입니다.