‘소프트웨어는 완성될 수 있다’는 도발적 아이디어를 통해, 무엇이 ‘완성’인지 정의하고 게임보이·임베디드 시스템·작은 자바스크립트 앱 사례를 살펴보며, 범위 고정, 의존성 줄이기, 정적 산출물, 품질 보증 등에서 얻을 교훈을 제안한다.
올해 제가 LoopConf에서 발표한 ‘25가지 교훈’ 중 하나는 “소프트웨어는 완성될 수 있다”였습니다.
발표에서 이 교훈을 빼서 다른 내용을 넣어 보려 했습니다. 그런데 이게 스스로 생명을 얻은 듯 발표에 꼭 들어가고 싶어 했죠. 중요한 교훈이었습니다.
“소프트웨어는 완성될 수 있다”는 주장은 논쟁적입니다. 그리고 발표에서도 분명히 했듯, 많은 경우 ‘완성된’ 소프트웨어는 목표가 아니어야 하고, 또 그럴 필요도 없습니다.
하지만 저는 이것을 하나의 아이디어, 이상, 이론으로 제시합니다. 씹어 볼 거리, 생각해 볼 거리로요.
완성된 소프트웨어는 어떤 모습일까요? 우리는 그것을 어떻게 만들까요? 그리고 이런 질문을 통해 우리가 소프트웨어를 만드는 방식에 대해 무엇을 배울 수 있을까요?
“소프트웨어”는 사실 아주 넓은 범위의 용어입니다. 기기에서 동작하는 저수준 펌웨어부터, 운영체제, 개발자만 쓰는 커맨드라인 도구, 독립적으로 실행되는 컴파일된 코드, 다른 소프트웨어—심지어 웹 브라우저 같은 완전히 별개의 애플리케이션!—가 있어야 실행되는 인터프리터 기반 코드까지 아우릅니다.
소프트웨어는 매우 다양한 형태를 띠며, 아주 다양한 목적을 위해 아주 다양한 방식으로 만들어지고 실행됩니다.
이 글에서 말하는 “완성된” 소프트웨어란 다음을 만족하는 것으로 하겠습니다.
이 정의에 맞춰 만들면 다음과 같은 함의가 있습니다.
플랫폼과 도구가 변하기 때문에 소프트웨어는 끝날 수 없다고 말할 수도 있습니다. 작동을 유지하려면 계속 업데이트해야 하니까요. 하지만 아마도 우리가 통제할 수 있는 것에 집중하는 게 중요할 겁니다.
이게 비합리적으로 보이나요? 너무 제한적으로 느껴지나요? 이 정의를 만족하는 꽤 규모 있는 소프트웨어는 도저히 만들 수 없다고 생각하실 수도 있습니다.
아마 맞을 겁니다. 그럴 경우라면, “완성된” 소프트웨어를 만들지 마세요. 그건 100% 괜찮습니다. 많은 비즈니스는 소프트웨어 접근 비용에 업데이트와 수정이 포함된다는 전제 위에서 운영됩니다. “완성된 소프트웨어”는—많은 경우—나쁜 비즈니스 모델입니다.
(정말 그럴까요? 저는 최근 기술에 밝지 않은 분이 이렇게 말하는 걸 들었습니다: “소프트웨어의 요점은, 한 번 뭔가를 만들고 그걸 무한히 복사해서 무한한 이익을 얻는 거잖아?”—소프트웨어를 바라보는 매우 흥미로운 관점이죠.)
계속 반복하지만, 실제 예시를 보기 전에 한 번만 더: 이 글은 여러분이 완성된 소프트웨어를 만들어야 한다고 말하지 않습니다. 다만 완성된 소프트웨어가 어떤 것일지 생각해 보는 과정에서 흥미로운 교훈을 얻을 수 있다는 점을 말하고자 합니다.
LoopConf에서 제가 청중에게 물었습니다. “제 집에서 찾을 수 있는 가장 오래된 소프트웨어가 뭐라고 생각하시나요?”
제가 알기로 그 답은 닌텐도 게임보이입니다.
이 기기(네, 오리지널을 가지고 있습니다)에 들어 있는 소프트웨어는, 글을 쓰는 지금으로부터 35년이 된 것으로 보입니다. 업데이트된 적이 없습니다. 버그를 겪어 본 적도 없습니다. 카트리지의 게임들도 마찬가지였습니다.
이는 사소한 소프트웨어가 아닙니다. 운영체제이고, 게임이기도 하죠! 안전이나 임무에 치명적인 시스템은 아니었습니다. 그럼에도 한 번 기기와 카트리지에 들어가면… 그걸로 끝이었습니다. 소프트웨어는 완료되었습니다.
영국 교육제도에서는 16세부터 18세까지 “A 레벨(A’ Levels)”을 공부합니다. 마지막 해 A 레벨 컴퓨팅 과목의 프로젝트로, 저는 부모님의 사업을 위해 파스칼(Pascal) 언어로 도구를 만들었습니다.
이 도구는 실제 비즈니스에서 정말로 사용되었습니다.
물론 A 레벨용으로 만든 버전이 최종판은 아니었습니다. 이후 몇 년 동안 조금씩 추가되었습니다. 하지만 어느 시점에서 저는 작업을 멈췄습니다.
그 도구는 의존성이 없는 x86(인텔 칩) 실행 파일로 컴파일되었고, 문자 기반 디스플레이를 사용했으며, 자체 바이너리 파일 포맷을 썼습니다.
데이터베이스의 마지막 입력은 17년을 사용한 뒤인 2012년 1월에 추가되었습니다.
Job Sheet Manager는 완성된 소프트웨어였습니다.

Job Sheet Manager – 데이터베이스의 마지막 입력
대부분의 집에는 세탁기, 냉장고, 냉동고, 식기세척기, 오븐, 레인지(호브), 보일러/난방기, 에어컨, 온도조절기, 전자레인지, 토스터, 주방·욕실 저울, 그리고 하나 이상의 시계가 있을 겁니다.
아마 오래된, 연결되지 않은 TV나 DVD 플레이어, 음악 시스템이 있을 수도 있죠.
아마 10년 동안 몸속에 들어 있을 심박조율기(페이스메이커)를 이식받았을 수도 있겠죠 (사실, 방금 찾아보니 요즘 페이스메이커는 펌웨어 업데이트가 가능하더군요. 꽤 놀랍습니다. 그러니 이 예시는 취소!).
이런 일반적인 전자 가전/기기 대부분에는 소프트웨어가 들어 있습니다. 이것이 “임베디드 소프트웨어”입니다. 그리고 이건 진짜 온 사방에 깔려 있습니다!!!! 스마트 홈이 아니더라도요.
제가 나열한 것들은 시작에 불과합니다.
전자 기기가 인터넷에 연결되어 있지 않다면, 그 안에서 돌아가는 소프트웨어는 거의 확실하게 한 번도 업데이트된 적이 없고 앞으로도 그럴 겁니다. 그 소프트웨어는 완성된 상태입니다.
이 주제로 대화를 나누다 한 친구가 이렇게 말했습니다. “환경이 절대 변하지 않는다고 가정해야겠지(예: 게임보이).” 맞는 말입니다. 이런 경우 소프트웨어는 특정 기기를 위해 만들어지고, 그 기기와 함께 살다가, 그 기기와 함께 수명을 다합니다.
하지만 괜찮습니다. 그것도 소프트웨어입니다(저는 펌웨어도 정의에 포함합니다). 그리고 여전히 ‘완성된’ 상태입니다.
어쨌든, 설령 환경이 변하더라도, 이 글에서 제가 던지는 질문은 이것입니다. 만약 환경이 변하지 않고, 내가 완성된 소프트웨어를 쓸 수 있다면 어떨까요? 그 과정과 최종 산물은 어떤 모습일까요?
마지막 예시로, 제 현재 분야인 웹 개발로 돌아와 보죠. 저는 ‘완성되었다’고 여기는 작은 웹사이트, “애플리케이션”, 라이브러리를 꽤 많이 만들었습니다.
복잡한 것도 있지만, 상당수는 비교적 작습니다.
예를 들면 다음을 만들었습니다.
저는 이들이 작고 단순하다는 점 자체가 ‘완성됨’이 지향하는 것 중 하나라고 봅니다.
한 프로젝트에 제한된 시간만 쓰되, 유지보수 없이도 계속 작동하길 바란다면 범위를 작게 유지하게 됩니다(예: Peekobot). 인프라를 없애기 위해 싸우게 됩니다(예: Untitled Word Game). 음… 스포일러가 너무 많아지네요.
이제 고려할 점/단순화할 점들을 살펴봅시다. ‘완성된’ 소프트웨어는 어떤 모습이며, 그 과정에서 배울 수 있는 사고방식은 무엇일까요?
이 목록은 불완전할 것입니다. 앞서 제시한 “완성됨”의 정의를 떠올리며 여러분 스스로도 생각을 이어가 보시길 권합니다. 무엇을 추가하시겠습니까? 무엇이 비합리적일까요?
저는 여기에 공식적인 프로세스를 가진 건 아닙니다—제가 만드는 ‘완성된’ 소프트웨어의 대부분은 혼자 꽤 무질서하게 진행하는 사이드 프로젝트입니다. 그러니 아래는 ‘완성된’ 소프트웨어에 담길 수 있는 아이디어 모음 정도로 봐 주세요.
무엇을 어떻게 할지 사전에 깊이 생각하세요.
시작하기 전에 가능한 많은 질문을 하세요. 진행하며 문서화하세요.
이 과정의 결과로 전혀 다른 제품을 만들거나, 아예 소프트웨어 제품을 만들지 않기로 결정할 수도 있습니다!
이 단계에서 이미 프로젝트의 다른 제약들을 떠올리게 될 겁니다. 좋은 일입니다.
이는 요구사항과 밀접히 연관됩니다. 하지만 여기서의 핵심은 제약을 받아들이면 창의성이 발휘될 수 있다는 점입니다.
게임보이는 4단계 회색조와 매우 제한된 메모리·연산 능력만 있었지만, 그 위에서 돌아가는 게임은 여전히 훌륭했습니다.
제 Peekobot 챗봇은 AI나 텍스트 분석 알고리즘을 쓰지 않고 ‘선택지 기반’으로 동작합니다.
제 스크래블 클론은 서버가 없어서 게임 데이터가 URL에 들어갑니다. (데이터를 인코딩해 URL에 넣는 이 캘린더 같은 도구를 보는 것도 흥미롭습니다.)
대부분의 임베디드 시스템은 고정된 사용자 인터페이스와 사용 가능한 하드웨어에서 파생된 매우 제한된 기능만을 가집니다.
여기서 ‘단순함’을 스스로에게 도전과제로 던지세요.
의존성은 어렵습니다.
그리고 의존성은 여러 곳에 존재합니다.
빌드 의존성. 나중에 어떤 시점에는 소프트웨어를 다시 빌드하지 못할 수도 있다는 점을 받아들일 수 있다면, 이건 그리 중요하지 않을 수 있습니다.
런타임 의존성. 피하도록 노력하세요(아래 “정적 산출물 만들기” 참조). 웹 개발의 예시를 들면:
플랫폼 의존성. 이건 더 다루기 어렵고, 실제로 강한 제약일 수 있습니다. 플랫폼은 변하고 궁극적으로 많은 경우 여러분의 소프트웨어를 깨뜨릴 겁니다. 하지만 그게 바로 요점이기도 합니다. 수명이 가장 긴 기술 스택은 무엇일까요? 그렇게 생각하면 여러분이 만드는 것이 어떻게 달라질까요? 예를 들어 웹 기반 프로젝트라면 최소한 호스팅을 고려해야 합니다. 호스팅 업체가 사라지면 어떻게 되나요? 다른 곳에 쉽게 재배포할 수 있나요?
이것은 의존성을 줄이는 것과 매우 비슷합니다. 하지만 의존성이 필요하다면, 외부 서비스나 플랫폼에 대한 실행 중 의존을 어떻게 없앨 수 있을까요?
위의 런타임 의존성 항목에서 이미 다뤘지만, 단순하고 잘 이해한 코드라면 프로젝트에 정적으로 포함시킬 수 있을까요? 신뢰할 수 있는 플랫폼 기능(제 세계에서는 바닐라 HTML, CSS, JS)을 라이브러리 대신 사용할 수 있을까요?
제 LoopConf 발표의 또 다른 “교훈”은 “버그를 고치지 말고—처음부터 만들지 마라!”였습니다.
애초에 소프트웨어에 버그가 들어갈 가능성을 줄이기 위해 어떤 도구와 프로세스를 사용할 수 있을까요? “천천히 움직여서 고칠 일이 없도록” 하려면 어떻게 해야 할까요?
린팅, 정적 분석, 테스트, 품질 보증은 모두 도움이 됩니다.
이해하기 쉬운 단순한 코드는 도움이 됩니다.
신뢰할 수 있는 팀의 좋은 코드 리뷰 프로세스도 도움이 됩니다.
그리고 품질은 “제대로 만드는 것”만큼이나 “올바른 것을 만드는 것”에 관한 것이기도 합니다. 요구사항에 대한 고민이 여기에도 이어집니다.
제로 버그가 불가능하다고 받아들인다 해도, 버그를 더 적게 만들기 위해 무엇을 할 수 있을까요?
소프트웨어 개발자로서 우리는 항상 어떤 의미에서든 “더 나은” 코드를 향해 나아가야 합니다.
프로그래밍은 공학이며, 공학은 트레이드오프의 예술입니다. 더 강한 재료는 더 무겁거나 더 비쌀 수 있으니, 정말로 그 강도가 필요할 때만 씁니다.
여기서 제가 드리고 싶은 건 ‘더 나음’에 대한 다른 관점입니다. 생각해 볼 또 다른 트레이드오프의 집합입니다.
우리는 정확하고, 버그가 없고, 빠르고, 안전하며, 정적으로 빌드되고, 의존성이 0이고, 맡은 일을 해내며 그 소프트웨어가 만들어진 플랫폼이 지속되는 한 계속 그 일을 해내는 소프트웨어를 꿈꿀 수 있습니다. 그런 것을 지향해야 할 것처럼 느껴집니다. Good Thing(TM)처럼 느껴집니다.
앞서 말했듯, 그리고 다시 강조하자면, 이런 트레이드오프가 여러분이나 여러분의 회사, 제품에 맞지 않을 수도 있습니다. 여러분은 아마 게임보이나 식기세척기를 만드는 게 아닐 겁니다. 완성된 소프트웨어가 목표가 아닐 가능성이 큽니다.
하지만 ‘완성된 소프트웨어’라는 개념 속에 새로운 아이디어가 있거나, 이 아이디어를 곱씹는 과정에서 새로이 중요해질 생각거리가 있을지 모릅니다.