Computer Science/Software Design

소프트웨어 디자인 : 설계 철학에 대하여

c4fiber 2025. 5. 12. 18:29

저는 카이스트 정글 수료생입니다. 권영진 카이스트 교수님의 OS 강의 중 추천해주신 영상을 정리해보았습니다.

정말 감명깊게 봤고 최대한 의미가 왜곡되지 않도록 작성해보았습니다만 정말 좋은영상이니 직접 보시는걸 추천드립니다.

 

A Philosophy of Software Design | John Ousterhout | Talks at Google

 

모든 내용은 강의자 입장에서 정리하였습니다.

제 개인적인 생각은 "인용구"를 통해서 작성하였습니다.


문제 쪼개기 (Problem Decomposition)

 

만약 당신이 Computer Science 분야에서 가장 중요한 컨셉을 하나 고른다면 무엇을 고를 것인가?

  • Abstraction (추상화)
  • Testing (테스트)
  • Complexity (복잡도)
  • Layers of Abstraction (추상화 계층, from 도널드 커누스)

나는 Problem Decomposition (문제 쪼개기) 라고 생각한다.

복잡한 문제를 어떻게 받아들여서 상대적으로(relatively), 독립적으로(independently) 구성할 것인가?

 

최근 회사업무를 수행하면서 데이터 파이프라인에서 데이터 중복 및 유실이 동시에 발생한 적이 있었습니다.
해당 증상을 디버깅 하기 위해서 가능성 여러가지를 소거법으로 하나하나 제거해가면서 찾아보았는데 문제 발생지점까지 도달하는데 약 5일 정도 소요되었습니다.
왜 이렇게 늦게 찾았나 생각해보니 상대적으로 관련이 없는 부분을 계속 의심하고 여러번 반복해서 디버깅 하다보니 오래 걸렸던 것 같습니다.
그래서 개인적으로는 "독립적으로" 문제를 작게 쪼개는게 중요하다고 생각합니다. 만약 제가 연관성이 적은 부분을 잘 나눠서 시간을 투자했다면 답에 도달하는데에 시간이 더 짧았을 것 같습니다.

 

가르쳐서 좋은 프로그래머로 만들 수 있는가? (Can great programmer be taught?)

 

우리는 10x 프로그래머 라며 평범한 개발자 보다 10배 이상의 능률을 보이는 개발자가 있다고 말합니다.

그런데 왜 이렇게 대단한 능력을 가르치려고 하지 않는걸까요? 가능하기는 한걸까요?

 

흔히 생산성 높은 뛰어난 프로그래머들은 재능이 있어서 스스로 깨우치고 성장한다고 생각합니다.

하지만 현실에서 Top 프로그래머와 Average 프로그래머의 차이점은 얼마나 연습했는가(practiced) 입니다.

 

이것이 유일하게 일관된 상관관계 (Consistent correlating factor) 입니다. 저는 이를 가르칠 수 있다고 생각합니다.

 

얕은 클래스 (Shallow Class)

 

초록부분 : 인터페이스(Interface)는 모든 사람이 알아야 할 내용이기 때문에 비용(Cost) 입니다.

파란 부분: 클래스의 내용은 기능을 실제로 제공하는 부분입니다. 즉 효과(Benefit) 입니다.

 

인터페이스가 크지만 실제로 제공하는 기능은 별로 없는 경우를 얕은 클래스(Shallow Class)라고 부릅니다. 전반적으로 부정적(net negative)인 결과를 가져옵니다만 절대 사용하지 말라는 뜻은 아닙니다. 하지만 복잡도(Complexity)와 싸우는데 도움이 되지는 않습니다.

 

인터페이스는 작으면서 제공하는 기능이 많은 경우는 깊은 클래스(Deep Class) 이다. 모든 클래스는 Deep Class가 되어야 한다.

왜냐하면 추상화(Abstraction)의 한 방법이고, 추상화는 정보 은닉(Information hiding)으로 이루어지기 때문이다. David Parnas 가 작성한 논문이 아주 좋으니 참고해보면 좋다.

 

전형적인 얕은 클래스

전형적으로 보이는 얕은 클래스의 예시이다. 정보 은닉이 없다. 게다가 함수를 호출하는데 실제로 실행되는 함수보다 타자를 더 많이쳐야한다.

 

**질문 : 그럼 class에 기능을 여러가지 넣는다면 인터페이스를 설계할 때 미리 여유롭게 설정해야 하는가?

**답변: 소프트웨어는 미래를 시각화 하는게 불가능하다. 인터페이스를 설계할 때 사용 목적에 특화되어 있을 것이기 때문에 여유가 거의 없을 것이다. 어느정도 여유를 두는건 괜찮지만 너무 두는건 좋지 않다. 어차피 문제를 해결하기 위해 인터페이스를 바꾸게 될 것이다.

 

존재하지 않는 에러를 정의하라

 

사람들은 더 많은 예외상황을 처리하려고 노력한다. 그래서인지 내가 에러를 잘 막아내고 있는것처럼 생각한다.
하지만 예외상황(Exception)이 많을 수록 더 많은 버그를 만들어내고, 연계되는 다른 예외상황을 만들어낼 수도 있다.

ex 1) Tcl unset

내가 만든 Tcl Script의 unset 함수는 변수를 삭제하는 명령어였다. 많은 사람들이 존재하지도 않는 변수를 삭제하려고 해서 에러가 발생했고 이를 exception으로 처리했다. 하지만 사람들은 지속적으로 이런행위를 반복했다.

내가 했어야 하는 일은 "의미 재정의(redefine semantics)" 였다.

unset 명령어를 변수를 "삭제"하는게 아니라 "사라지게" 만들었다. 기존에 없던 변수면 이미 없고, 존재하는 변수는 사라지게 만들면 된다.

 

ex) UNIX File Deletion

이미 열려있는 파일을 삭제하려면 그 파일을 사용하고 있는 프로그램을 모두 종료시켜야 한다. 하지만 그 프로그램을 못찾는다면? 그래서 시스템을 재부팅하고 파일을 삭제하려고 시도한다. 그런데 재부팅을 해도 시스템 데몬이 사용하고 있다면? 
UNIX는 아름다운 방법을 사용했다. 파일을 삭제하면 namespace, directory에서만 삭제해서 파일시스템에서는 찾을 수 없게 했다. 마지막 프로그램이 파일을 읽기를 끝내면 그때 정리한다.

 

내가 생각하기로는 소프트웨어 디자인에서 중요한 부분은 무엇이 중요한지, 중요하지 않은지를 찾는것이다.
이상적으로는 가능한 작은 부분이 중요하게 여겨져야한다. 정말 중요하고 문제가 되는 부분을 찾아서 당신의 시스템에 반영해야 한다.

 

** 질문: Exceptions vs Return Value 어떤걸 사용해야 하는가?

** 답변 : stack에서 멀리 보내려면 exception, 한단계씩 보내려면 return value

 

최근에 Rust를 배우면서 exception vs return value 에 대해서 다시 한번 생각해보고 있습니다.
exception 의 경우는 여러 stack을 한번에 넘어가기 때문에 시간소요가 상대적으로 크고, 흐름을 추적하기 어렵게 만듭니다. 때문에 Rust는 exception을 사용하지 않고 Panic 과 Result type를 도입하였습니다.
exception handler를 잘 정의해서 프로그램이 중단되지 않도록 하는것도 좋지만, 애초에 exception이 발생하지 않는 방법이 없는지 고민해보는것도 좋다고 생각합니다.

 

Tactical vs Strategic Programming

 

내가 생각하기에는 좋은 디자인의 가장 큰 장애물은 마인드셋이다. 마인드셋 없이는 절대 좋은 디자인을 내놓을 수 없다.

개발자 대부분은 전술적(Tactical) 접근을 한다.

  • 목표: 다음 기능, 버그 고치기
    문제는 최대한 깔끔하게 만들겠지만 몇가지 지름길(short cut)을 만들고.. kluges 만들고... 임시방편을 만들고...
    팀의 모든 사람들이 이러한 형태를 갖추게 된다면 이를 고치는데 수맣은 시간과 노력이 들어갈 것이고 매우 빠르게 스파게티 코드로 변화할 것이다.

문제는 complexity는 한 하나의 실수때문에 발생하는게 아니라 수많은 실수로 인해서 발생한다.
물론 시간을 들여서 처리할 수 있겠지만 결국은 압도당해서 절대 하지 않게 될 것이다.

Tactical Tornado: 혼자서 수많은(약 80%만 작동하는) 저품질의 코드를 생산하는 사람을 말한다. 그리고는 wake of destruction을 그 뒤에 남긴다.
대부분의 조직에서는 이러한 사람들이 영웅으로 받아들여진다. 겨우 내일까지 작동하지만 심지어 이런사람들이 10x 프로그래머라고 생각하는 사람도 있다!

이처럼 tactical한 접근은 매우 빠져들기 쉬우며 그러지 않기 쉽지않다.
좋은 디자인을 하기 위해서는 작동하는 코드로는 충분하지 않다는걸 깨달아야 한다.
작동하는 코드는 단 하나의 목표가 될 수 없다. not single goal, table stakes

 

you have to invest. 내 의견으로는 결국 투자의 결과는 다 돌아오게 된다.

초반엔 얼마나 천천히 가야하는가? 언제서야 tactical한 접근을 따라잡을 수 있을까? -> 내 의견으로는 왜 이러한 접근을 해야하는지 깨닭고 난 후 6 ~ 12개월정도 걸린다고 생각한다.

 

정말 고민을 많이 한 부분이기도 합니다. 
제 경우에는 고민만 하다가 실제로 코드를 작성하지 못하고 끝낸경우가 너무 많았기 때문에 균형을 맞출 목적으로 Tactical을 지향해보았습니다만 이것도 생각만큼 쉽지 않았습니다.
그래도 가장 중요하게 생각하는 건 "기한내에 완성하기" 입니다. 물론 균형을 놓치지 않아야겠죠!

 

얼마나 투자할 것인가? (How Much To Invest?)

 

 

망가진, crappy한 코드로도 성공은 할 수 있다. 하지만 그러지 않고도 성공할 수 있다. Google, VMware는 그러지 않고도 성공했다.
이런 좋은 문화를 가지고 있다면 최고의 프로그래머들을 끌어모으는데 꽤 좋은 위치에 서있게 될 것이다.
우리는 10x 현상을 알고있다. 더 좋은 제품을, 더 빠르게 만들고 제공하기 위해서는 최고의 프로그래머들을 영입하는 것이다. 그래서 좋은 디자인 문화를 강하게 지지한다면 최고의 인재를 고용할 수 있게 해준다고 생각한다. (I think the strongest argumentin favor of a good design culture is that it allows you to hire top people)

 

얼마나 투자할 것인가?를 결정하려면 얼마나 투자할 수 있는가 너 자신에게 물어라 "나는 내 인생에서 이 단계에 얼마나 투자할 수 있는가?" 나는 대략 10% ~ 20% 라고 생각한다. 적어도 10%는 투자할 수 있을 것이다. 매몰비용이 아니다. 반드시 돌아온다.


영웅적인 큰 투자가 아니라 조금씩의 꾸준한 투자. 모든 시스템을 완전히 디자인하기위해 6개월을 투자할 수는 없다. 조금씩 점진적으로 투자해야한다. 그래서 모듈을 새로 만든다면 인터페이스를 설계할 때 조금의 시간을 투자하고 deep classes와 함께하도록 시도해라. 어떻게 진행되는지 문서로 작성하고 , 단위테스트도 물론이다.

 

처음엔 잘 모를것이다. 제대로 안되는게 당연하다. 그게 소프트웨어의 룰이다. 항상 무언가를 발전시키고, 증진시킨다고 생각하라. 항상 더 좋게 만들 수 있도록 살펴봐라.

첫번째 이유는 당신은 아마도 무언가를 만들때 망치게 될 것이다. 그래서 이를 적어도 반반으로 만들고 싶으면 무언가를 발전시켜야 한다. (One reason for this is you're brobably making something worse when you go in also. So even if you just want to break even, you've got to find something to improve) 그래서 나는 그저 반반으로 만들려고 노력하는 것이라고 생각하는 편이다.


일반적으로는 사람들이 존재하는 코드에서 개선사항을 찾을때, 가장 적은줄의 코드로 변경할 수 있는 가능성을 찾는다. 내가 생각하기엔 그들은 그저 겁먹은 거라고 생각한다. (난 이해하지 못하고, 뭔가 부숴버릴것 같고, 그래서 global variables 들을 사용해서 점프하고..) 그러지 마라. 깔끔한 방법(clean way)을 시도하고, 찾아봐라. 스크래치나 당신이 알고있는 것을 통해 모든 시스템을 구성하고 작동시킨다면 굉장히 이상적이다. 하지만 항상 그렇진 못하고, 당신이 감당할 수 있는 것보다 더 큰 스케일을 경험할 것이다.

 

당신에게 한마디 한다면 내가 할 수 있는 최고의 부분(point)은 무엇인가? 내가 할 수 있는 최선을 다하고 있는가?를 물어라. 그냥 boss를 위해 내일까지 완성시킨다고 just hack 하지 말고 내가 가능한 최선을 다하고 있는지 물어라.

 

**질문 : layers of abstraction 과 performance가 충돌할 때 어떤 기준으로 어느쪽을 선택하는가?

**답변: 우리는 복잡도(Complexity)를 관리하기 위해서 레이어가 필요하다. 이럴땐 꽤 좋다.
속도(성능)에 문제가 있다면 내가 생각하기엔 사람들은 너무 많은 레이어를 만든다. 내 생각엔 몇몇개의 두터운 레이어 보다 수많은 얇은 레이어들을 만들어서 전형적인 실수라고 생각한다.
두번째는 성능적으로 문제가 있는 몇몇의 경우에는 핵심적인 성능 매트릭스(metrics)가 문제인데, 내 생각에는 적절한 레이어와 함깨 충분히 해결할 수 있는 문제라고 생각한다.
디자인을 하면서 생각해보고 최고의 성능을 레이어링과 함께 성취할 수 있도록 시스템 디자인을 하면서 고민해봐야 한다고 생각한다.


Google Talk를 마치면서 이런 이야기를 합니다. "단 한곳에서만 쓰이는 클래스라도 살짝은 범용적으로 설계해야 한다는 것이다. 실제로 클래스를 간단하고, 쉽게 (simpler, deeper)하게 만들게 된다."

 

지금 당장 필요한 기능이 동작만을 위한 코드를 작성하기를 반복하면 Tactical Tornado가 되기 쉽습니다. 조금 여유를 만들어 고민해보고 프로그래밍하며 성장했으면 좋겠습니다. (물론 제한시간 내에 문제를 해결하는 능력은 매우 중요합니다! 하지만 조금만 여유를 두고 생각해보자는 뜻입니다.)

 

도움이 되었길 바랍니다.