소프트웨어 개발을 공학으로 만드는 것
개인의 탁월함에 기대지 않는 프로세스
KO | EN
소프트웨어를 만드는 직업에는 여러 이름이 있다. 개발자라는 이름이 가장 흔하고, 프로그래머라는 이름은 중립적이면서 약간은 전통적인 느낌을 준다. 코더는 말 그대로 코딩하는 사람을 의미하는 이름이지만, 소프트웨어 개발에 수반되는 수많은 과정에서 수동적인 코드 작성만을 담당한다는 뉘앙스로 인해 멸칭으로 쓰이기도 한다. 그리고 그 반대편에는 소프트웨어 엔지니어가 있다.
소프트웨어 엔지니어라는 이름은 사뭇 다른 느낌을 준다. 왠지 모르게 코더보다 전문적일 것 같고, 어째서인지 프로그래머보다 수학을 잘할 것 같고, 뭔가 개발자보다 중요한 일을 할 것 같다. 심지어 캐나다의 일부 주에서는 (원칙적으로) “소프트웨어 엔지니어”, “컴퓨터 엔지니어” 및 "엔지니어"라는 명칭이 붙은 정보 처리 분야의 직함은 해당 주의 엔지니어링 규제 기관으로부터 엔지니어 자격을 취득한 경우에만 사용할 수 있다. 많은 사람들이 엔지니어라는 직함을 들으면 그 사람이 공인된 전문직 자격이나 면허를 가졌다고 생각하기 때문이다.
이 모든 것은 엔지니어라는 이름이 주는 주관적인 '느낌’에 대한 이야기지만, 많은 사람들이 비슷한 느낌을 공유하고 있다면 왜 그런 느낌이 드는지 고민해볼 필요가 있다. 애초에 소프트웨어 개발이 자명하게 공학으로 불릴 만하다면 소프트웨어 엔지니어라는 이름에도 특별함이 없었을 것이다. 이런 느낌이 드는 이유는 무엇이고, 소프트웨어 공학이 기계 공학, 전기 공학, 토목 공학과 왜 다르게 느껴지며, 소프트웨어 개발을 공학으로 만드는 것은 또 무엇일까?
소프트웨어와 공학
NATO Software Engineering Conference (Robert McClure, Brian Randell)
공학에 대한 하나의 정의가 있는 것은 아니지만, 일반적으로는 인간의 요구를 충족하기 위해 다양한 제약 안에서 수학적, 과학적 지식을 응용하여 체계적으로 문제를 해결하는 분야라고 할 수 있다. 유네스코[1]와 미국국립학술원[2]은 각각 다음과 같이 공학을 정의한다.
Engineering is the field of practice, profession and art that relates to the development, acquisition and application of technical scientific and mathematical knowledge. It is about the understanding, design, development, invention, innovation and the use of materials, machines, structures, systems and processes for specific purposes.
공학은 기술적, 과학적, 수학적 지식의 개발, 습득 및 응용과 관련된 실천, 전문직, 예술 분야다. 이는 특정한 목적을 위해 재료, 기계, 구조물, 시스템 및 프로세스를 이해하고, 설계하고, 개발하고, 발명하고, 혁신하고, 사용하는 것을 의미한다.
Engineering is both a knowledge of the creation and design of human-made products and processes and a problem-solving method called design under constraint.
공학은 인간이 만든 제품과 프로세스의 창조 및 설계에 관한 지식인 동시에, 제약 조건 아래에서의 설계라고 불리는 문제 해결 방법이다.
소프트웨어 개발을 다른 공학 분야처럼 체계화하려는 시도는 컴퓨터 시대의 초기부터 있었다. 소프트웨어 공학의 출발점은 흔히 1968년 NATO 소프트웨어 엔지니어링 컨퍼런스(NATO Software Engineering Conference)[3]로 여겨진다. 독일에서 열린 이 컨퍼런스에서는 소프트웨어의 규모와 중요성이 급격히 증가하면서 복잡도가 높아지고 있다는 문제의식이 반복적으로 등장했는데, 이것을 소프트웨어 위기(software crisis)라고 부른다. 컨퍼런스 참가자들 사이에 합의된 결론은 없었지만, 아직 발전 단계에 있는 소프트웨어 개발의 당면 문제를 해결하려면 기존 공학 분야 수준의 방법론과 체계가 필요하다는 생각은 널리 공유되고 있던 것으로 보인다. 컨퍼런스 보고서의 편집자들은 소프트웨어 공학이라는 표현에 대해 이렇게 썼다.
The phrase ‘software engineering’ was deliberately chosen as being provocative, in implying the need for software manufacture to be based on the types of theoretical foundations and practical disciplines, that are traditional in the established branches of engineering.
'소프트웨어 공학’이라는 표현은 의도적으로 도발적인 의미를 담아 선택했는데, 이는 소프트웨어 개발이 기존 공학 분야에서 전통적으로 요구되는 이론적 토대와 실무적 규율에 기반해야 한다는 점을 암시하고자 했기 때문이다.
한편 토마스 헤이그(Thomas Haigh)는 컨퍼런스에 앞서 ALGOL 60의 후속 언어를 만들던 IFIP Working Group 2.1 내부에서 일어난 논쟁을 더욱 중요한 지점으로 본다[4]. 그룹의 주류는 적은 수의 기본 개념을 조합해 복잡한 프로그램을 표현할 수 있어야 한다고 생각했다. ALGOL 68 초안에는 이러한 사상이 강하게 반영되었는데, 그룹 내에서 프로그램의 표현보다 복잡한 프로그램을 신뢰할 수 있게 만드는 방법을 중시한 이들은 ALGOL 68의 방향에 반발했다. NATO 소프트웨어 엔지니어링 컨퍼런스의 중심에는 ALGOL 68 반대파가 있었다. 반대파는 앞으로 소프트웨어 개발이 더 엄밀하고 통제 가능한 활동으로 변모해야 한다고 봤다. 이들 중 한 명이었던 에츠허르 데이크스트라(Edsger Dijkstra)는 프로그래밍을 응용 수학의 일종으로 취급했고, 이후 소프트웨어 위기라는 단어를 다시 사용하면서 구조적 프로그래밍을 주장하기도 했다.
소프트웨어 공학에 관한 당시의 논쟁이 애플리케이션 프로그래머와는 동떨어져 있었다는 점에 유의해야 한다. 컨퍼런스 참석자는 대부분 연구자였고, 이 시기 대다수의 애플리케이션은 기업에서 비즈니스 데이터 처리를 위해 코볼(COBOL)로 작성한 재무/회계/인사 소프트웨어였다. 따라서 1968년 논의되던 소프트웨어 공학의 개념으로 당시 소프트웨어 업계 전반을 설명할 수는 없다. 하지만 오늘날 프로그래밍 교육을 받은 프로그래머들은 ALGOL 68 논쟁과 NATO 소프트웨어 엔지니어링 컨퍼런스의 영향을 간접적으로, 때로는 직접적으로 받고 있다. C 언어로 프로그래밍할 때 goto 구문을 사용하지 말라는 조언을 들어본 적이 있다면, 혹은 객체지향 프로그래밍이나 함수형 프로그래밍과 같은 주제를 접해봤다면, 하다못해 현대적인 프로그래밍 도구를 사용하고 있다면 말이다.
소프트웨어에 대한 공학의 적용
소프트웨어 개발을 공학으로 편입하려는 오랜 노력 끝에 Software Engineering Body of Knowledge (SWEBOK)[5]에서 채택한 '공학’과 '소프트웨어 공학’의 정의는 다음과 같다.
The Institute of Electrical and Electronics Engineers (IEEE) defines engineering as “the application of a systematic, disciplined, quantifiable approach to structures, machines, products, systems or processes”. (…) software engineering is defined as “the application of a systematic, disciplined, quantifiable approach to the development, operation, and maintenance of software; that is, the application of engineering to software.”
전기전자공학자협회(IEEE)는 공학을 구조물, 기계, 제품, 시스템 또는 프로세스에 대한 체계적이고, 규율 있으며, 정량화 가능한 접근법의 적용으로 정의한다. (…) 소프트웨어 공학은 소프트웨어의 개발, 운영, 유지보수에 대한 체계적이고, 규율 있으며, 정량화 가능한 접근법의 적용, 즉 소프트웨어에 대한 공학의 적용으로 정의된다.
이 정의에 따르면 소프트웨어 개발을 공학으로 만드는 것은 소프트웨어 개발에 공학적 접근을 적용하는 것이다. 이때 공학적 접근이란, 엔지니어가 문제를 해결하는 데 실현 가능한 다양한 해법 중 하나를 일정 기준에 따라 선택해 실행한 뒤 모니터링하는 프로세스를 의미한다. 이 과정은 순차적일 필요는 없지만, 반드시 반복적이어야 한다. 사실 공학적 접근은 본질적으로 반복적이다. 어떤 단계에서든 새롭게 획득한 지식이 이전 단계와 연관될 수 있으며, 이것이 자연스럽게 반복을 유발하기 때문이다. 공학적 접근에서 의사결정은 추정에 기반하므로, 결정의 품질은 추정의 품질에 좌우된다. 따라서 엔지니어는 추정치를 실제 결과와 비교해 추정의 피드백 루프를 만들어야 한다. 추정과 실제 결과 사이의 괴리를 유발하는 요인을 이해하면 추정 기법을 다듬어 미래에 더 정확한 추정을 할 수 있다. 그리고 이를 통해 계속해서 더 나은 의사결정을 내릴 수 있게 된다.
직관을 공학으로 설명하기
다소 상아탑에 갇힌 이야기처럼 들리니 구체적인 예시를 생각해보면 좋을 것 같다. 어느 날 인터넷 커머스 서비스를 개발하는 프로그래머가 서비스 운영팀으로부터 상품 상세 페이지의 로딩이 너무 느리니 개선해달라는 요구를 받는다. 시스템을 잘 알고 있는 프로그래머는 로딩이 느리다는 말에 직관적으로 아이디어를 떠올릴 수도 있다. 이것이 항상 잘못된 결정으로 이어지는 것은 아니지만, 왜 그 결정이 다른 대안보다 타당한지, 얼마나 성능을 개선해야 성공이라고 할 수 있는지, 실제로 사용자에게 얼마나 효과가 있을지 설명하기는 어렵다.
공학적 접근의 시작은 실제 문제를 정확히 이해하는 것이다. "너무 느리다"에는 많은 의미가 있으므로 우선 개선이 필요한 상품 상세 페이지의 문제를 측정해야 한다. 지표를 살펴보니 상품 상세 페이지의 최근 FCP p75 지표가 2,700ms다. 사용자 경험을 위해 권장되는 FCP p75는 1,800ms 이하[6]이므로 이를 목표로 삼을 만하다. 프로그래머는 트레이스를 분석해 SSR 서버가 페이지를 렌더링하는 동안 상당한 시간을 상품 상세 API의 응답을 기다리는 데 소비했다는 사실을 확인한다. 상품 상세 API의 p95 레이턴시는 1,000ms였고, 느린 요청의 경우 800ms가 데이터베이스의 읽기 쿼리를 기다리는 데 소요되었다. 이제 문제를 "로딩이 느리다"가 아니라 "읽기 중심의 상품 조회에서 데이터베이스 쿼리가 레이턴시를 유발하는 병목이 되고 있다"로 다시 정의할 수 있다.
이 문제에는 다양한 해법이 있다. 인메모리 캐시 레이어를 도입할 수도 있고, 쿼리나 인덱스를 개선할 수도 있고, 읽기 레플리카에 쿼리하도록 변경할 수도 있고, 페이지를 통째로 정적 렌더링해 CDN으로 서빙할 수도 있다. 각 해법에는 각각의 장단점과 트레이드오프가 있다. 중요한 것은 첫 번째로 떠올린 해법이 정답이라고 섣불리 결론짓지 않는 것이다. 트래픽을 분석해보니 전체 상품 중 상위 5%의 인기 상품이 조회 요청의 약 90%를 차지하고 있고, 캐시 TTL을 시뮬레이션해보면 레디스를 도입했을 때 95% 이상의 캐시 히트율을 기대해볼 수 있다. 레디스의 읽기 레이턴시는 수 밀리세컨드로 알려져 있으니 캐시가 적중하면 레이턴시를 200ms 수준까지 절감할 수 있을 것 같다.
프로그래머는 다른 대안을 비교한 뒤 레디스를 이용해 인메모리 캐시 레이어를 만드는 것이 가장 효과적일 것이라고 판단했다. 레디스 도입 후 지표는 캐시 히트율 65%, API의 p95 레이턴시 850ms, FCP p75 2,000ms로 소폭 개선되었다. 실제 결과와 추정치가 왜 다른지 확인해보니 상품 정보가 개인화 데이터에 의존하고 있어 캐시 키의 카디널리티가 예상보다 컸다. 프로그래머는 정적인 상품 정보는 글로벌 캐시에 저장하고, 개인화 데이터는 별도로 조회하도록 캐시 전략을 재설계함으로써 캐시 히트율을 97%로, API의 p95 레이턴시를 200ms로, FCP p75를 1,400ms로 개선한다. 문제를 측정하고, 다양한 해법을 비교하고, 결과를 추정하고, 실제 결과를 추정과 비교해 개선하는 이 모든 과정에 공학적 접근이 적용되었다.
공학적 접근이 필요한 순간
이렇게 보면 소프트웨어 개발에 공학적 측면이 있음이 분명하다. 그럼에도 소프트웨어 개발이 다른 공학 분야와 다르게 느껴지는 이유는, 공학의 정의에 부합하지 않는 방식으로도 적당히 잘 동작하는 소프트웨어를 만들 수 있기 때문일 것이다. 프로그래머는 체계적이지 않게 설계할 수 있고, 규율 없이 코딩할 수 있으며, 정량화 불가능한 방식으로 소프트웨어를 완성할 수 있다.
개발 과정이 어떻든 동작하는 소프트웨어를 만들 수 있기 때문에 실제 소프트웨어 개발 현장에서 공학적 접근은 쉽게 간과되곤 한다. 소프트웨어 개발 분야는 짧은 기간 동안 급격히 변화했다. 흔히 컴퓨터 공학의 기초 지식이라고 불리는 것들마저 다른 공학 분야가 기초하는 자연 법칙에 비하면 훨씬 빠르게 변화한다. 그렇다보니 많은 소프트웨어 개발 조직이 올바른 원칙을 따르기보다는 속도에 집중해왔다. 그 결과 소프트웨어의 중요성과 치명성에 비해 완성도는 너무나 쉽게 후순위로 취급되곤 한다. 공급자는 '소프트’웨어라는 이름답게, 문제가 생겨도 수정하기 쉽다는 특성이 소프트웨어의 낮은 완성도를 정당화할 수 있다고 생각할지 모르지만, 소비자 입장에서 믿을 수 없는 소프트웨어를 사용하는 것은 매우 불쾌한 경험이고, 어떤 경우에는 위험하기까지 하다.
소프트웨어 개발에 대한 조금 다른 관점도 있다[7]. 페테르 나우르(Peter Naur)는 프로그래밍의 진정한 목표는 프로그램이라는 산출물을 만드는 것이 아니라, 프로그래머의 마음속에 이론을 구축하는 것이라고 주장했다[8]. 나우르는 프로그래머의 마음에 형성된 지식과 이해를 길버트 라일(Gilbert Ryle)을 인용해 '이론’이라고 표현했다. 프로그램은 프로그래머의 내면에 존재하는 정신적 구성물의 표현이므로 이론을 텍스트로 옮길 때는 정보 손실이 불가피하다. 따라서 프로그래머를 잃는 것은 프로그램을 잃는 것과 같다. 셰리 터클(Sherry Turkle)과 시모어 페퍼트(Seymour Papert)는 일단 작동하는 소프트웨어를 만들고 그것을 바탕으로 변경과 관찰을 반복하는 프로그래머를 브리콜뢰르(bricoleur)[9]라고 지칭하며 이들의 프로그래밍 방식에 주목했다[10]. 저자들은 소프트웨어의 전체 구조를 미리 계획하고 분해하여 구현하는 방식이 아니라, 실제로 작동하는 소프트웨어와 상호작용하며 프로그래밍하는 방식으로도 완성도 높은 소프트웨어를 만들 수 있다고 보았다. 또한 소프트웨어 가드닝(gardening)은 소프트웨어를 살아 있는 생물로 취급하여 소프트웨어의 예측 불가능성을 수용한다. 이 관점에 따르면 프로그래머는 주인의식과 직관에 따라 변화하는 소프트웨어라는 정원을 섬세하게 가꾸는 전문가다.
이처럼 소프트웨어 개발에 대한 다양한 관점이 있지만, 그럼에도 소프트웨어 개발 조직에는 공학적 접근이 필요하다. 공학적 접근이 완벽한 소프트웨어를 보장하지는 않지만, 최소한 품질의 하한은 높일 수 있기 때문이다. 조직에 마음속의 이론을 구축한 숙련된 프로그래머가 계속 머물 수도 없고, 모든 프로그래머가 탁월한 직관을 가진 브리콜뢰르나 제품에 대한 주인의식이 가득한 정원사일 수도 없다. 조직은 균일하지 않은 개개인의 역량 위에서 일관되게 높은 완성도를 갖춘 소프트웨어를 생산해야 한다. 공학적 접근의 강력한 힘은 타고난 재능이 부족해도 잘 설계된 체계적인 프로세스를 따른다면 좋은 결정을 내릴 확률을 높일 수 있다는 데 있다. 모든 문제에 공학적으로 접근해야 하는 것은 아니다. 하지만 단 하나의 정답이 아닌 다양한 해법이 병존하는 문제를 다룰 때는 트레이드오프를 비교하고, 해법을 선택해 실현하는 과정에 공학적 접근을 적용함으로써 더 나은 결과물을 기대할 수 있다. 소프트웨어 개발 조직이 개인의 천재성에 의존하지 않고 반복해서 신뢰할 수 있는 결과를 만들어야 하는 순간부터 공학적 접근이 필요해진다.
소프트웨어 개발을 공학이라고 말하는 데 필요한 조건은 문제의 난이도나 엔지니어의 자격과는 무관하다. 소프트웨어 개발에 공학적 접근을 적용하는 것이 소프트웨어 개발을 공학으로 만든다. 엔지니어는 문제를 해결하기 위해 추정하고, 실험하고, 측정하고, 재현하고, 개선하고, 반복한다. 그리고 이런 방식으로 소프트웨어를 만들어 문제를 해결하는 사람을 우리는 소프트웨어 엔지니어라고 부른다.
소프트웨어 공학의 종말
소프트웨어 개발에 공학적인 접근을 적용하고자 한 첫 움직임으로부터 60여 년이 지난 지금, 소프트웨어 공학은 종말론에 휩싸였다. 인공지능으로 인해 전통적인 소프트웨어 공학의 접근법이 더 이상 프로그래머에게 실질적인 도움을 줄 수 없다는 것이 종말론의 근거다. 여기서 인공지능이 LLM을 말하는 것이라면, 아마 소프트웨어 공학은 쉽게 종말하지 않을 것이다. 종말론이 진짜 예언하는 것은 인간 소프트웨어 엔지니어의 종말이다.
아직까지는 인간 프로그래머가 주도적으로 소프트웨어 개발에 공학적 접근을 적용했을 때 유의미한 효과가 있다. 첫 번째 이유는 인간이 기술적 제약을 비롯한 환경적 맥락과 구체적인 요구사항을 인공지능에게 입력해야 하기 때문이다. 인공지능에게 추상적인 비즈니스 요구사항을 입력하면 추상적인 결과물이 만들어진다. 엣지 케이스를 제대로 다루지 못하는 결과물을 미세하게 다듬기 위해 인공지능과 오랜 시간 씨름하다 보면 결국 코드가 가장 명확한 비즈니스 명세라는 사실을 깨닫게 된다. 두 번째 이유는 인공지능이 출력하는 결과물의 품질이 일관되지 않고, 충분히 신뢰할 수 없기 때문이다[11]. 특히 브라운필드 시스템에서는 한계가 두드러져 인간 소프트웨어 엔지니어가 개입해 인공지능의 결과물이 요구사항과 제약을 만족하는지 측정, 검증하는 과정이 필수적이다. 이러한 이유로 소프트웨어 개발 현장에서 인간이 주도하는 소프트웨어 공학은 여전히 인간과 인공지능 모두로 하여금 일관되게 더 나은 품질의 소프트웨어를 생산할 수 있도록 유도하는 하네스(harness)를 제공하고 있다.
시대적 전환기에는 누구나 그럴듯한 예언을 할 수 있다. 언젠가는 인공지능이 자율적으로 현실 세계의 제약을 파악해 요구사항을 정의하고, 완벽히 문제를 해결해내는 블랙박스화된 소프트웨어를 만들어낼지도 모른다. 벤 슈나이더먼(Ben Shneiderman)은 인간에게 더 많은 제어권을 제공하면서도 더 높은 자동화를 달성하는 인공지능 시스템을 일관되고, 안전하며, 신뢰할 수 있는(Reliable, Safe & Trustworthy, RST) 시스템이라고 제시했다[12]. 소프트웨어 개발의 자동화에만 집중해 훗날 인간의 제어 없이 소프트웨어가 만들어진다면, 그렇게 만들어진 소프트웨어에 어떠한 인간도 책임이 없다면, 그리고 지금 우리가 그런 소프트웨어를 내심 바라고 있다면 종말론은 자기실현적 예언이 될 것이다. 적어도 지금은 소프트웨어 엔지니어 스스로 미래를 결정할 수 있다.
- [1]
“Basic Sciences, Research, Innovation and Engineering”, unesco.org.
- [2]
- [3]
Peter Naur, Brian Randell eds., “Software Engineering”, 1969.
- [4]
- [5]
- [6]
- [7]
여기에서 소개하는 관점들도 소프트웨어 개발에 대한 공학적 접근을 전면으로 부정하지 않는다. 페테르 나우르는 NATO 소프트웨어 엔지니어링 컨퍼런스 보고서의 편집자로서 도발적으로 소프트웨어 공학이라는 용어를 소개하기도 했다.
- [8]
- [9]
브리콜뢰르(bricoleur)는 손에 닿는 도구와 재료들을 이용해 그것이 원래 무엇을 위해 만들어졌든 상관없이 직면한 문제를 해결하는 지혜로운 사람을 의미하는 프랑스어 명사다.
- [10]
- [11]
- [12]
Ben Shneiderman, “Human-Centered Artificial Intelligence: Reliable, Safe & Trustworthy”, 2020.