| source | https://refactoringenglish.com/blog/software-essays-that-shaped-me/#bonus-brad-fitzpatrick-on-parsing-user-input-2009 |
|---|---|
| created | 2026-03-22 |
| by | nvidia:glm5 |
내가 첫 프로그래밍 일자리를 얻기 20년 전부터 소프트웨어 블로그를 읽기 시작했다. 그동안 소프트웨어에 관한 블로그 게시물과 에세이를 수천 편이나 읽었지만, 내 머릿속에 남아 사고방식을 바꾼 것은 손에 꼽을 정도다.
조엘 스펄스키(Joel Spolsky)는 역사상 가장 뛰어난 소프트웨어 블로거다. 그의 에세이는 내 소프트웨어 접근 방식에 지대한 영향을 미쳐서 하나만 고르기가 참 어려웠지만, “조엘 테스트(The Joel Test)”를 가장 좋아한다.
조엘 테스트는 고용주가 소프트웨어 팀에 얼마나 잘 투자하고 있는지 확인하기 위해 스스로에게 물어볼 수 있는 12가지 질문이다.
- 소스 제어를 사용하는가?
- 한 번의 단계로 빌드를 만들 수 있는가?
- 매일 빌드하는가?
- 버그 데이터베이스가 있는가?
- 새 코드를 작성하기 전에 버그를 수정하는가?
- 최신 상태의 일정이 있는가?
- 사양서(spec)가 있는가?
- 프로그래머가 조용한 작업 환경에서 근무하는가?
- 돈을 주고 살 수 있는 최고의 도구를 사용하는가?
- 테스터가 있는가?
- 새로운 지원자가 면접 중에 코드를 작성하는가?
- 복도에서 사용성 테스트를 수행하는가?
일부 질문은 시대에 뒤떨어졌지만, 중요한 것은 질문 그 자체가 아니라 질문이 담고 있는 메시지다. 조엘은 사실 고용주에게 이렇게 묻고 있었다. “개발자를 존중하는가?”
이 질문들은 모두 고용주가 값싼 사무실 공간이나 단기 마감일보다 개발자의 시간과 집중을 우선시하는지 평가한다. 조엘은 닷컴 버블이 절정에 달하던 시기에 이 글을 발표했다. 당시 숙련된 개발자는 귀한 자원이었지만, 개발자 자신을 포함해 모두가 그 사실을 자각하지는 못했다.
조엘의 블로그는 항상 프로그래머를 고용주가 구하고 보살펴야 할 희귀하고 섬세한 천재로 묘사했다. 나는 그 점이 마음에 들었다. 경력 내내 나는 조엘 테스트 점수가 좋은 고용주를 찾아다녔고, 그들을 찾을 수 있는 지도를 준 조엘에게 감사하고 있다.
이 에세이는 하스켈(Haskell)의 타입 시스템을 활용하는 내용을 다룬다. 잠깐! 자지 마라. 타입 시스템이나 하스켈에 관심이 없다면 이해한다. 나도 그렇다. 하지만 이 에세이는 내가 소프트웨어를 생각하는 방식을 완전히 바꿨다.
알렉시스의 기법은 정적 타입을 지원하는 어떤 언어(예: Go, C++, 러스트(Rust))에서도 하스켈 밖에서 사용할 수 있다.
에세이의 핵심 내용을 아주 짧게 요약하면, 데이터를 검증할 때마다 이를 새로운 타입으로 변환해야 한다는 것이다. 애플리케이션에서 사용자 이름을 최대 20자의 영숫자로 제한하는 규칙이 있다고 가정하자. 순진한 해결책은 다음과 같은 함수를 정의하는 것이다.
func validateUsername(username string) error { ... }
위 함수를 사용하면 사용자로부터 사용자 이름을 받을 때마다 validateUsername을 실행한다. 이 접근 방식의 문제는 코드가 기본적으로 안전하지 않다는 점이다. 받는 모든 사용자 이름을 검증해야 하는데, 검증하지 않고 사용자 이름을 처리하는 코드 경로를 실수로 만들기 쉽다. 악의적인 사용자가 이 실수를 발견하면, 사용자 이름 필드에 악성 코드를 심거나 10억 자를 넣어 서버 자원을 고갈시키는 등 교묘한 짓을 할 수 있다.
알렉시스의 해결책은 대신 다음과 같은 함수를 사용하는 것이다.
func parseUsername(raw string) (Username, error) { ... }
코드베이스의 나머지 부분에서는 string 타입인 “username”을 전달하는 대신 커스텀 타입인 Username을 사용한다. Username을 생성할 수 있는 유일한 함수는 parseUsername이며, 이 함수는 Username 인스턴스를 반환하기 전에 검증 규칙을 적용한다. 따라서 Username 인스턴스가 있다면 그 안에는 반드시 유효한 사용자 이름이 들어 있다. 그렇지 않다면 존재할 수 없기 때문이다. 신뢰할 수 없는 입력은 항상 string이고, Username을 기대하는 함수에 string을 전달할 수 없으므로 사용자 이름 검증을 잊을 수가 없다.
알렉시스의 에세이를 읽기 전까지 나는 타입 시스템이 언어 광들의 주의를 딴 데로 돌리는 재미있는 수단이라고 생각했다. “파싱하세요, 검증하지 말고”는 컴파일러 기능이 애플리케이션의 보안과 안정성을 높이는 데 얼마나 가치 있는지 눈을 뜨게 해주었다.
대학 시절, 나는 프레드 브룩스(Fred Brooks)의 소프트웨어 공학 에세이 모음집인 맨먼스 미신(The Mythical Man-Month)을 읽었다. 이 책은 그가 IBM의 OS/360 프로젝트를 이끌었던 경험을 담고 있다. 에세이 중에는 그른 것도 있었다. 2002년 기준으로도 너무 오래되어 관련성이 떨어지는 것들도 있었지만, 내 머릿속에 남은 것은 “은탄환은 없다”였다.
이 에세이는 소프트웨어 작업을 본질적 복잡성(essential complexity)과 우연적 복잡성(accidental complexity) 두 가지로 나눌 수 있다고 주장한다.
본질적 복잡성은 도구나 하드웨어와 상관없이 반드시 수행해야 하는 작업이다. 예를 들어, 영업사원 보너스를 계산하는 소프트웨어를 작성한다면, 보너스 공식을 정의하고 가능한 모든 예외 상황을 처리해야 한다. 이 작업은 50억 달러짜리 슈퍼컴퓨터를 쓰든 1달러짜리 마이크로컨트롤러를 쓰든 동일하다.
우연적 복잡성은 그 외의 모든 것이다. 메모리 누수를 처리하거나, 코드가 컴파일되기를 기다리거나, 서드파티 라이브러리 사용법을 알아내는 것 등이 여기에 해당한다. 도구와 하드웨어 자원이 좋을수록 우연적 복입성에 쏟는 시간이 줄어든다.
이 모델을 바탕으로 브룩스는 도구나 하드웨어의 어떤 발전도 개발자 생산성을 10배 향상시키는 것은 불가능하다고 결론지었다.
소프트웨어 엔지니어가 지금 하는 일 중 본질적인 것이 아닌, 우연적인 것에 얼마나 많은 노력을 쏟고 있는가? 모든 노력의 10분의 9 이상이 우연적인 활동에 해당하지 않는다면, 우연적 활동을 전부 제거한다고 해도 한 자릿수 개선은 불가능하다.
내 경력 내내 사람들은 소프트웨어에서 프로그래머를 없애려는 시도를 해왔다. 몇 년 동안 노코드(no-code) 플랫폼은 숙련된 웹 개발자의 모든 능력을 비프로그래머에게 제공하겠다고 약속하며 화제를 모았다.
브룩스의 에세이는 최신 유행어로 불리는 플랫폼이 본질이 아닌 우연에 집중하기 때문에 개발자를 대체할 수 없다는 사실을 항상 확인시켜 주었다. 플랫폼이 기능 명세에서 마법처럼 작동하는 코드를 만들어 낸다고 해도, 여전히 누군가는 명세를 작성해야 한다.
나는 소프트웨어를 만드는 어려운 부분은 이 개념적 구성물의 명세, 설계, 테스트이지, 이를 표현하고 표현의 정확성을 테스트하는 노력이 아니라고 믿는다.
최신 AI는 브룩스의 이론에 빗금을 긋고 있다. AI는 실제로 본질적 복잡성을 줄여주기 때문이다. 불완전하거나 모순되는 명세를 AI에 넘겨주면, AI는 비슷한 명세를 표절하여 빈틈을 채운다. AI가 우리가 아는 프로그래밍을 없앤다고 해도, 브룩스의 에세이는 어떤 추상화 수준에서든 본질적 복잡성을 관리할 사람은 여전히 필요할 것이라는 희망을 준다.
위에서 조엘 스펄스키의 에세이 중 가장 좋아하는 것 하나를 고르기 어렵다고 말했는데, 그래서 두 편을 선택했다. “선택”은 사용자 인터페이스를 만들 때 사용자에게 권한을 주는 것이 갖는 미묘한 비용에 관한 이야기다.
모든 선택지는 사용자에게 결정을 요구한다. 즉, 사용자는 무언가를 생각하고 결정해야 한다. 이것이 반드시 나쁜 것은 아니지만, 일반적으로 사람들이 내려야 할 결정의 수를 최소화하도록 항상 노력해야 한다.
예를 들어, 조엘은 윈도 98에서 도움말 문서를 검색하려고 할 때 나타나는 터무니없는 대화 상자를 공유한다.
이 대화 상자는 사용자가 도움을 받으려 하는데 방해하고, 데이터베이스 최적화에 관해 정보가 부족한 상태에서 결정을 내리도록 요구하기 때문에 조엘을 화나게 한다. 윈도는 결정을 회피하고 그것을 사용자에게 떠넘긴 것이다.
조엘의 에세이는 그래픽 사용자 인터페이스에 초점을 맞추지만, 나는 사람들이 내 코드를 접할 수 있는 모든 곳, 즉 명령줄이나 내가 작성한 함수를 호출하는 다른 개발자 등에서 이를 생각한다. 사용자가 관심을 갖는 것에 대한 권한은 여전히 주면서, 사용자를 대신해 유용한 결정을 내릴 수 있을까?
조엘의 에세이는 내가 직접 내릴 수 있는 결정을 사용자에게 떠넘기는 것으로부터 나를 수없이 구해주었다.
레이먼드 첸(Raymond Chen)은 마이크로소프트 윈도 팀에서 가장 오래 근무한 개발자 중 한 명이다. 그의 블로그에는 윈도 프로그래밍 역사에 관한 유익하고 재미있는 이야기가 수천 개나 있지만, 내가 가장 자주 떠올리는 것은 윈도 비스타의 호환성 모드에 관한 이야기다.
한 고객이 레이먼드의 팀에 다음과 같이 문의했다.
안녕하세요, 저희는 원래 윈도 XP와 윈도 서버 2003용으로 설계된 프로그램이 있는데, 윈도 비스타에서 실행하면 문제가 발생합니다. 프로그램을 윈도 XP 호환성 모드로 설정하면 윈도 비스타에서 잘 실행되는 것을 확인했습니다. 사용자가 윈도 비스타에서 실행할 때 자동으로 윈도 XP 호환성 모드로 실행되도록 설치 프로그램에서 어떤 변경을 해야 할까요?
레이먼드는 고객의 요청을 다음과 같이 묘사했다.
저는 보통 애완동물 가게 앞 인도에 쓰레기를 버립니다. 매일 아침 가게가 문을 열면 누군가가 쓰레기를 쓸어서 쓰레기통에 버리죠. 그런데 일요일에는 애완동물 가게가 문을 열지 않아서, 일요일에는 쓰레기가 그냥 쌓여 있습니다. 애완동물 가게가 일요일에도 문을 열게 하려면 어떻게 해야 할까요?
나는 이 비유를 좋아했다. 이 은유는 너무 웃겨서 나는 방금까지 레이먼드가 틀렸다는 것을 눈치채지 못했다. 그는 단 한 번의 릴리스 후에 윈도가 자신들의 앱을 망가뜨리지 않을 것이라고 기대한 개발자를 비웃고 있다. 하지만 레이먼드 첸의 글 대부분이 그렇듯, 이 글도 너무 재미있고 날카로워서 결점을 넘어설 수 있다.
구체적인 내용에는 동의하지 않지만, 레이먼드의 게시물은 사용자 행동에 영향을 주는 훌륭한 교훈이다. 사용자가 당신에게 도움이 되는 일을 하도록 유도하려면, 사용자 관점에서 저항이 가장 적은 경로가 무엇인지 신중하게 생각하라. 사용자는 그 경로를 선택할 것이기 때문이다. 사용자에게 인도에 쓰레기를 버리는 것이 문제를 완전히 해결한다는 것을 보여주면, 사용자는 계속 인도에 쓰레기를 버릴 것이다.
나는 항상 단위 테스트를 좋아했고 테스트 코드에 큰 자부심을 느꼈다. 그래서 이 에세이가 내 화장실에 나타났을 때 내내 내 경력 동안 끔찍한 테스트를 작성해 왔다는 사실을 알게 되어 충격을 받았다.
에릭의 에세이는 미묘한 버그가 있는 다음 단위 테스트를 보여준다.
@Test public void shouldNavigateToPhotosPage() {
String baseUrl = "http://plus.google.com/";
Navigator nav = new Navigator(baseUrl);
nav.goToPhotosPage();
assertEquals(baseUrl + "/u/0/photos", nav.getCurrentUrl());
}
에세이를 처음 읽었을 때 나는 “그게 바로 내가 단위 테스트를 작성하는 방식이잖아!"라고 생각했다. http://plus.google.com/ 문자열을 두 곳에 중복해서 뭐 하러 넣는가? 프로덕션 코드처럼 단일 소스의 진실(single source of truth)을 만들면 된다. 나는 항상 이렇게 했다. 테스트에서 중복을 없애기 위해 헬퍼 함수, 변수, 루프를 추가했다.
위 접근 방식의 문제는 미묘한 버그를 감춘다는 점이다. 실제로는 URL이 다음과 같아야 한다.
http://plus.google.com//u/0/photos
^^ 이런
에릭의 에세이는 테스트 코드를 프로덕션 코드처럼 취급하면 안 된다는 것을 보여주었다. 둘은 완전히 다른 목표와 제약을 갖는다. 좋은 테스트 코드는 무엇보다 명확해야 한다. 테스트 코드는 자체 테스트 코드가 없으므로 정확성을 검증하는 유일한 방법은 검사뿐이다. 테스트는 독자에게 어떤 동작을 검증하는지 명백하게 드러내야 한다. 이 목표를 위해 복잡성을 줄이기 위해 중복을 수용할 수 있다.
소프트웨어 엔지니어로서 나는 웹 분야에 진입하는 것이 부끄러울 정도로 늦었다. 경력의 첫 10년 동안 데스크톱 앱과 백엔드 서버 코드만 작성했다. 2017년가 되어서야 HTML이나 자바스크립트를 신경 썼다.
프론트엔드 개발을 배우는 것을 진지하게 고려했을 때, 자바스크립트는 10일 만에 짜깁기해서 만든 엉망인 언어라는 인상을 받았고, 브라우저마다 동작이 크게 달랐다. 웹 앱을 작성하려면 자바스크립트의 모든 결점과 문제점으로부터 나를 보호해 줄 현대적이고 세련된 무언가가 필요했다.
그래서 당시 인기 있던 웹 프레임워크인 앵귤러(Angular), 리액트(React), 뷰(Vue)를 시도했다. 뷰를 배워서 어느 정도 쓸 수 있게 되었지만, 의존성 문제와 프레임워크의 함정에 막대한 시간을 쏟았다. 이 프론트엔드 프레임워크들이 자바스크립트를 고치려고 모든 노력을 기울였지만, 웹 프로그래밍은 여전히 형편없었다.
그때 줄리아의 에세이를 읽고 자바스크립트가 고침이 필요하다고 확신한 나머지 기회를 한 번도 주지 않았다는 것을 깨달았다. 당시 나는 TinyPilot의 프로토타입을 작업하고 있었는데, 이것은 내 첫 상업적으로 성공한 소프트웨어 제품이 되었다. TinyPilot은 웹 인터페이스가 있었고 뷰로 구현할 계획이었지만, 줄리아의 에세이는 순수 자바스크립트만으로 얼마나 많은 것을 할 수 있는지 시도해 보도록 영감을 주었다. 프레임워크도, 래퍼 라이브러리도, 빌드 단계도, Node.js도 없이, 그저 평범한 옛날 자바스크립트만 사용했다.
사실 “옛날”은 아니다. ES2018에 가깝지만, 알다시피 그런 것이다. 나는 언젠가 프레임워크나 빌더 같은 것이 필요한 문제에 부딪힐 것이라고 계속 예상했지만, 그런 일은 일어나지 않았다. 웹 컴포넌트(WebComponents)와 관련된 몇 가지 함정은 여전히 있었지만, 뷰와 앵귤러에서 겪은 고통에 비하면 아무것도 아니었다.
프레임워크에서 자유로워지는 것이 좋았다. 런타임 오류가 발생하면 스택 추적은 내 코드가 축소되고 변형되고 트리 쉐이킹된 악몽 같은 형태가 아니었다. 내가 작성한 그대로의 내 코드를 디버깅했다. 왜 이걸 더 일찍 시도하지 않았을까?
자바스크립트에 대한 내 편견은 틀렸다. 현대 자바스크립트는 꽤 괜찮다. 래퍼 라이브러리의 많은 아이디어를 흡수했으므로 이제는 래퍼가 필요 없다. 그리고 브라우저도 플랫폼과 기기에서 일관된 동작을 보장하도록 제 몫을 다했다. 2020년 이후 어떤 새 프로젝트에도 자바스크립트 프레임워크나 빌드 단계를 통합하지 않았고, 한 번도 뒤를 돌아보지 않았다. 순수 자바스크립트는 프레임워크가 주는 혜택의 90%를 5%의 두통으로 얻을 수 있다.
이것은 이 목록에 넣기에 이상한 에세이이다. 실제로 읽어본 적이 없기 때문이다. 사람들이 이 에세이를 인용해 줬고, 아이디어를 이해하고 나니 너무 직관적이라서 읽을 필요가 없었다.
CoRecursive 팟캐스트 진행자 아담 고든 벨(Adam Gordon Bell)과의 인터뷰에서 그는 일단 아이디어를 이해하면 제목만 있으면 충분한 논픽션 책이 있다고 말했다. “지루한 기술을 선택하라”는 나에게 그런 책이다.
댄의 주장은 새 프로젝트를 시작할 때 소문이 자자한 최신 기술을 사용하고 싶은 유혹에 빠진다는 것이다. 구글이 엑사바이트까지 확장되는 새로운 데이터베이스를 발표했는데, 비용은 20%로 성능은 40% 더 빠르다고 하자. 이런 멋진 새로운 대안이 바로 있는데 포스트그레스큐엘(Postgres)을 쓴다면 바보가 아닐까?
실제로는 새로운 기술에 버그와 약점이 있지만, 아직은 누구에게도 분명하지 않다. 그래서 그것에 부딪히면 꼼짝없이 갇히게 된다. 포스트그레스큐엘에도 문제가 있지만, 30년 동안 현장에서 쓰이면서 마주칠 법한 모든 문제에 대한 검증된 해결책이 있다.
댄은 때로는 새로운 기술을 사용해야 하지만 전략적으로 그리고 제한된 양만 사용해야 한다고 인정한다. 그는 모든 비즈니스에 쓸 수 있는 세 개의 “혁신 토큰(innovation token)”이 있다고 제안한다. 화려한 새 데이터베이스를 원하면 토큰 하나를 써야 한다.
댄의 에세이는 줄리아의 에세이와 자연스럽게 연결된다. 프론트엔드 프레임워크에 그 모든 시간을 낭비하기 전에 둘 중 하나라도 읽었더라면 좋았을 것이다.
테렌스 이든(Terence Eden)은 즐겁고 다채로운 기술 블로거다. 그는 매주 몇 편의 새로운 게시물을 쓰지만, 내게 가장 큰 영향을 준 것은 “디지털 삶에서 완전히 차단당했다”였다.
이 글은 번개가 테렌스의 집을 때려 모든 소유물이 파괴된다면 어떤 일이 벌어질지 시뮬레이션한다. 그는 모든 비밀번호를 비밀번호 관리자에 보관하지만, 모든 기기가 파괴되면 비밀번호 관리자에 접근할 수 없다. 하드웨어 패스키(passkey)도 집에 있었으므로 그것도 사용할 수 없다.
나는 항상 데이터를 중복 드라이브에 저장하고, 세 대륙에 두 공급업체를 통해 오프사이트 백업을 유지하므로 데이터에 대해 꽤 안전하다고 느꼈다. 테렌스의 게시물은 모든 기기를 동시에 파괴할 수 있는 신빙성 있는 위협, 즉 화재, 홍수, 전기 서지, 범죄 수사 등을 생각하게 만들었다. 내 모든 데이터는 내 머릿속에 있는 비밀번호로 암호화되어 있으므로, 기억 상실, 무능력화, 사망도 그 목록에 추가된다.
온라인 서비스는 사용자가 재해에서 복구하는 것을 도와주는 데 서툴다. 나는 휴대전화를 잃어버리는 일이 결코 없을 것이라고 가정하는 여러 서비스를 이용한다. 이메일 계정과 소유한 모든 디지털 기기를 잃어버리는 경우는 더욱 고려하지 않는다.
테렌스의 에세이를 읽은 이후, 어떤 서비스와 기기가 나에게 중요한지, 그리고 테렌스가 묘사한 시나리오에서 어떻게 복구할 수 있을지 더 많이 생각하고 있다. 다음에 노트북을 샀을 때, 나는 도서관에서 설정하면서 집에 있는 어떤 기기 없이도 비밀번호 관리자와 중요 계정에 접근할 수 있는지 테스트했다.
디지털 재해 대비 측면에서 여전히 개선할 점이 있지만, 테렌스의 게시물은 기기와 데이터를 보호하는 방법을 생각할 때마다 항상 내 머릿속에 메아리친다. 만약 모든 것이 갑자기 파괴된다면?
엄밀히 말하면 에세이는 아니지만, 소프트웨어 인터뷰에서 나온 인용구를 나는 끊임없이 생각한다. 2009년, 조엘 스펄스키의 격찬 어린 리뷰 덕분에 (네, 또 조엘이다) 나는 숙련된 프로그래머와의 인터뷰를 모은 Coders at Work를 읽었다.
라이브저널(LiveJournal)과 멤캐시드(Memcached)의 창작자인 브래드 피츠패트릭(Brad Fitzpatrick)이 인터뷰 대상자 중 한 명으로 등장한다. 당시 그는 28세로, 책에 실린 프로그래머 중 가장 어렸고 가장 욕을 많이 하고 가장 재미있는 사람이었다.
소프트웨어 공학의 윤리에 관한 질문에 대한 답으로, 브래드는 입력 검증에 관한 열정 어린 폭언을 쏟아냈다.
모든 사람이 신용카드 양식에서 빌어먹을 공백이나 하이픈을 넣도록 일관되게 허용해 주었으면 좋겠어요. 컴퓨터는 그런 거 지우는 걸 잘하거든요. 내 번호를 어떻게 포맷하라고 지시하지 마요.
- Coders at Work 속 브래드 피츠패트릭
나는 웹 폼에 전화번호를 붙여넣으려고 할 때마다 괄호나 공백이 허용되지 않는다고 불평하는 경우 이 인용구를 떠올린다. 더 나쁜 것은 괄호 때문에 전화번호가 잘리고, 동시에 괄호가 허용되지 않는다고 불평하는 것이다.
내 소프트웨어에서 입력 필드를 만들고 예상치 못한 문자를 생각할 때마다, 나는 브래드 피츠패트릭이 “컴퓨터는 그런 거 지우는 걸 잘하거든요”라고 말하는 것을 듣는다.