컴퓨터 과학의 모든 문제는 다른 수준의 간접 계층으로 해결할 수 있다 - David J. Wheeler
... 그러나 그러면 또 다른 문제가 생기는 것이 일반적이다. - Butler W. Lampson
개발자는 추상과 구조에 대한 이야기를 많이 할수 밖에 없다. 우리는 이미 수많은 추상화 계층 속에서 프로그램을 만들고 있으며 그 안에서도 생산성, 가독성 등 여러 이유로 추상화하고 구조를 만들어 나간다. 이러한 추상과 구조는 개발자의 사고에 큰 영향을 미친다.
추상화와 구조화는 많은 문제를 일으키기도 하지만 개발자의 강력한 무기가 될 수도 있다. 이 글에서는 추상과 구조가 무엇인지 알아보고 개발자가 이를 어떻게 활용할 수 있는지 알아볼 것이다.
개발자가 하는 일
개발자가 하는 일을 한 마디로 정의하자면, "프로그램을 만드는 것"이다. 프로그램은 원하는 결과를 이루기 위해 수행되는 일의 계획1이라는 뜻을 가지고 있다. 원하는 결과를 이룬다는 것에는 문제를 해결한다라는 숨은 뜻이 있다. 따라서 개발자는 프로그램을 만들어 세상의 다양한 문제를 해결하는 자랑스러운 직업이라 할 수 있다.
그런데 우리는 왜 문제를 해결할까? 개발자가 문제를 해결하는 것은 비즈니스를 위함이다. 여기서 말하는 비즈니스란 꼭 돈을 벌기 위한 것만을 의미하는 것은 아니다. 비즈니스는 사람들이 편리하게 살 수 있도록 하는 것이며, 사회적 가치를 창출하는 것이다. 그래서 개발 업무는 비즈니스에서 다루는 방법론과 유사한 경우가 많다. 그런 관점에서 바라보았을 때 문제 해결 프로세스는 다음과 같이 이루어진다.
이런 프로세스는 범위와 기간의 차이가 있을 뿐 소프트웨어 공학에서 다루는 폭포수, 스파이럴, 애자일 등 어떤 방법론을 사용하더라도 크게 다르지 않다. 그리고 역량과 책임의 정도나 역할에 따라 차이는 있을 수 있지만 개발자는 이 프로세스 전체에 관여할 수 있고 관여하기를 권장한다.
결국 개발자는 코드 구현만 하는 사람이 아니라 문제 해결자라고 봐야한다. 그래서 개발자는 기술을 익히는 것도 중요하지만 문제를 해결하는 사고 방식을 익히는 것도 중요하다.
시니어 개발자는 풍부한 경험과 직관을 통해 문제를 능숙하게 해결할 수 있다. 그러나 경험과 직관에 의존하면 분명한 한계가 존재한다. 경험해보지 못한 문제를 발견하거나 그 경험 자체가 잘못됐을 수 있다. 따라서 어떠한 문제를 발견하더라도 유연하게 해결할 수 있는 방법론을 익히는 것이 중요하다. 그리고 이를 통해 경험과 지식 범위를 벗어나더라도 다양한 도메인, 환경에 빠르게 적응할 수 있다. 이러한 방법론이 바로 이 글의 주제인 추상적, 구조적 사고다.
추상적 사고
개발자는 프로그램을 만들기 위해 고려해야 할 것이 매우 많다. 요구사항 수집 같은 것을 빼고 보더라도 좋은 코드를 작성하기 위해 필요한 지식이 한 두 가지가 아니다.
그렇지만 이 개발자의 지식과 관련된 모든 것은 추상적이고 구조적인 생각과 연관이 있다. 대체 추상적, 구조적 사고가 뭐길래 이렇게까지 말하는 걸까? 먼저 추상적 사고에 대해 알아보자.
추상화란?
추상화라는 단어는 많이 들어봤을 것이다. 학창 시절 미술 시간에 들어봤을 수 있고 코딩을 처음 배울 때 클래스를 다루며 들었을 수도 있다. 본론부터 말하자면 추상(Abstraction, 抽象)은 여러가지 혹은 하나의 사물이나 개념에서 공통되는 특성이나 속성을 추출하는 것을 말한다. 보통 개발자가 추상을 다룰 때는 '공통'이라는 키워드에 집중하는 경우가 많다. 하지만 여기서는 '추출'이라는 키워드에 집중해보자.
위 이미지의 공통점은 무엇일까? 정답은 모두 지구 표면을 표현한 물건이라는 점이다. 차이점은 다음과 같다.
- 지구본은 지구의 둥근 모양을 그대로 표현하고 있다. 즉, 현실 세계와 가장 유사하다.
- 메르카토르 도법은 2차원 직사각형으로 세계를 표현하기 위한 방법이다. 직관적으로 보기 편하지만 면적이 왜곡된다.
- 로빈슨 도법은 2차원이지만 왜곡을 최소화시켜 표현하기 위한 방법이다. 면적이 왜곡되지 않지만 직사각형이 아니다.
왜 뜬금없이 지도 얘기를 하냐고 할 수도 있겠지만 이것이 바로 추상화의 예시다. 지구는 둥근 모양이지만 우리는 이를 현실 세계에 표현해야 하기 위해 지구의 특성을 추출하여 다양한 방법으로 표현한다. 그리고 이를 관심에 따른 추상화라고 할 수 있다. 즉, 추상화는 필요한 특성을 추출하여 우리가 원하는 목적을 달성하기 위한 방법이다.
단순화와 재해석
정리하자면 추상의 핵 심은 단순화하고 재해석하는 것이다. 이를 통해 복잡한 문제를 단순하게 만들고 새로운 관점에서 바라볼 수 있게 한다. 이러한 추상화는 개발자가 프로그램을 만들 때도 중요한 역할을 한다. 그런데 단순화하고 재해석은 어떻게 할 수 있을까? 뭔가 직관적으로 해야할 것처럼 보이지만 간단한 원칙이 있다.
르네 데카르트는 "나는 생각한다, 고로 나는 존재한다"라는 말로 유명한 철학자다. 하지만 다른 철학적 사고로도 유명한데 그것은 모든 문제를 가장 작은 단위로 분해하라는 것이다. 이는 요소 환원주의라고 말하며 높은 단계의 개념은 한 단계 더 낮은 요소로 분해하여 정의하는 사고 방식을 의미한다. 예를 들면, 사람은 팔, 다리, 몸통으로 나눌 수 있고 더 나누면 피부, 근육, 뼈 등으로 나눌 수 있다. 결국 마지막엔 분자, 원자가 되는 식이다.
추상화하는 과정을 도식으로 나타내면 위와 같다. 먼저 현상 혹은 물체가 존재하고 이를 단계 별로 분석하고 해체한다. 그리고 목적에 따라 결합하여 최종적인 결과물을 만들어 낸다. 다만, 주의할 것은 무의미할 정도로 과한 분해는 지양해야 한다. 사람을 분자, 원자까지 분해하는 것이 의미가 있을까? 대부분은 아니다. 따라서 적절한 수준이라는 것이 필요하다.
조금 더 구체적인 예시를 들어보자. 다음과 같은 인물 사진이 있다. 이 사진을 분해하고 결합해보자.
위 사진을 분해할 수 있는 요소는 색상, 구도, 선, 빛 등 다양하다. 이렇게 분해하고 다시 목적에 따라 결합하면 다음과 같은 결과물이 나올 수 있다.
어디서 본 것처럼 느껴지지 않는가? 아마 유저 아이콘이나 기본 이미지 등에서 본 것과 비슷하게 느껴질 것이다. 즉, 추상화는 다양한 곳에서 적용할 수 있는 사고 방식이며 어떻게 추상화해야 할지 고민된다면 시간이 더 걸리더라도 위와 같이 단순화하고 재해석하는 과정을 거쳐보자.
동작 추상화
추상화는 물체가 아닌 행위에도 적용할 수 있다. 예를 들어, 지금 우리가 피자를 먹고 싶다고 가정해보자. 이때 피자를 먹기 위해 피자를 만드는 과정을 진행할 수 있다. 피자를 만드는 과정은 다음과 같다.
- 피자 도우와 토핑을 위한 재료를 준비한다.
- 강력분, 드라이이스트, 소금, 설탕, 물, 올리브유 ...
- 토마토 소스, 올리브유, 베이컨, 바질, 치즈 ...
- 오븐을 예열해둔다.
- 피자 도우를 반죽한다.
- 강력분, 드라이이스트, 소금, 설탕, 올리브유를 잘 섞어 덩어리를 만든다.
- 덩어리를 랩으로 덮어서 실온에 1~2시간 발효시킨다.
- 밀가루를 바닥에 뿌리고 반죽을 펴서 가스를 빼야한다.
- 도우에 올리브유를 바르고 토마토 소스를 바른다.
- 재료를 토핑한다.
- 예열된 오븐에 180도로 20분 정도 구워준다.
- 완성 후 먹는다.
완전히 구체적이진 않지만 그래도 실행해볼 수 있을 정도로는 구체적이다. 그럼 위 과정을 도식화해보자.
아까보다는 훨씬 추상적이다. 이것만봐서는 피자를 만들 수 없지만 어떤 순서로 진행해야 하는지는 알 수 있다. 만약 피자 만들기 문서를 만든다면 위 도식을 먼저 보여준 후 구체적인 과정을 설명하는 것이 좋을 것이다. 하지만 우리의 목적은 피자를 먹는 것이므로 한 번 더 추상화를 해보자.
사실 위 과정을 모두 '피자 주문하기'라는 단계로 추상화할 수 있다. 조금 황당하게 느껴질 수 있지만 실제로 개발하면서 위와 같은 추상화를 할 때가 많다. 목적을 달성하기 위해 특정 동작을 하는 시스템에 행동을 위임하는 것이다. 그런 방식을 통해 우리는 해당 시스템의 구체적인 동작을 몰라도 원하는 목표를 달성할 수 있다. 다만, 구체적인 동작을 모른다는 것이 문제가 될 수 있다. 우리가 원하는 토핑이 없을 수도, 적절한 굽기 시간이 아닐 수도 있다. 그래서 항상 추상화를 할 때는 적절한 수준을 유지하고 필요한 경우에는 구체화할 수 있어야 한다.
추상화 수준
추상화는 수준(Level)이 중요하다. 너무 높은 수준의 추상화는 구체적인 문제 해결에 어려움을 줄 수 있고 너무 낮은 수준의 추상화는 문제를 이해하는데 어려움을 줄 수 있다. 추상화는 진짜 모습을 왜곡하고 단순화하는 것이기 때문에 이러한 문제가 발생할 수 밖에 없다.
따라서 추상화 수준을 정하는 것은 매우 중요하며 사실상 추상화의 핵심이라 할 수 있다. 추상화 수준을 잘 정하기 위해선 추상화의 목적이 무엇인지를 확실히 정의하고 구체적으로 알아야하는 것과 아닌 것을 분리해야 한다.
구조적 사고
이어서 구조적 사고에 대해서 알아보자. 구조적 사고는 간단하다. 내용을 겹치지 않고 빈틈없이 정리하는 것이라고 정리할 수 있다. 구조적 사고는 추상적 사고와도 밀접한 관련이 있다. 올바른 수준으로 추상화된 것을 분리하여 더이상 신경쓰지 않게 만들 수 있다. 즉, 복잡성을 줄일 수 있다는 뜻이다. 추상화가 문제를 단순화하고 재해석하는 것이라면 구조화는 문제를 조직화하고 분류하는 것이다. 이 둘을 함께 사용하면 문제를 더 잘 이해하고 해결할 수 있다.
구조화를 하는 방법
구조화를 하는 방법을 알아보기 위해 예시를 하나 들어보자. 다음 일부러 복잡하게 쓰여진 글이 있다.
무엇에 관한 이야기인가?
그릇을 준비한다. 이것을 A라고 부르자. 냄비를 준비한다. 이것을 B라고 부르자. B에 물을 채우고 가스레인지 위에 올려라. A 안에 버터와 초콜릿을 넣는다. 버터는 100g, 초콜릿은 185g을 넣어야 하고 70% 다크 초콜릿이어야 한다. A를 B위에 놓고, A의 내용이 녹을 때까지 거기에 두고, 다 녹으면 A를 내려놓는다. 다른 그릇 하나를 준비한다. 이것을 C라고 부르자. C에 계란, 설탕, 바닐라 에센스를 넣는데, 계란은 두 알, 설탕은 185g, 바닐라 에센스는 반 티스푼을 넣는다. C의 내용물을 섞는다. A의 내용물이 식으면 C에 넣고 섞는다. 다른 그릇을 준비한다. 이것을 D라고 부르자. D를 가지고 밀가루, 코코아 분말, 소금을 넣는데, 밀가루는 50g, 코코아 분말은 35g, 소금은 반 티스푼을 넣는다. D의 내용을 완전히 섞은 후에 체를 통해 C에 넣는다. D의 내용을 완전히 결합하기에 충분할 정도로만 섞는다. 그건 그렇고, 초콜릿 브라우니를 만들고 있는데 한 가지 잊은 것이 있다. D를 가지고 70g의 초콜릿 칩을 넣고, D의 내용물을 서로 충분히 합해질 정도로만 섞는다. 베이킹 캔을 준비한다. 이것을 E라고 부르자. 기름과 E를 베이킹 페이퍼로 연결한다. D의 내용물을 E에 넣는다. 오븐을 F라고 부르자. 참, F를 160도로 예열해야 한다. E를 F에 20분 동안 넣은 다음 F에서 E를 꺼낸다. 몇 시간 동안 식힌다.
— 좋은 코드, 나쁜 코드 中
위 글은 끝까지 읽기 힘들 정도로 정리가 안된 글이다. 무엇에 관한 이야기인지 알려면 글을 끝까지 봐야알 수 있다. 참고로 이 글은 초콜릿 브라우니를 만드는 방법에 대한 글이다. 무엇이 문제일까?
- 전체를 읽기 전에 어떤 내용인지 알 수 없다.
- 단계가 없기 때문에 순서를 알기 힘들다.
- 'A'와 같은 알기 힘든 이름을 사용하고 있다.
- 중요한 일이 나중에 나오고 있다.
이 문제를 해결하기 위해 구조적 사고를 사용할 수 있다. 독자가 잘 읽을 수 있게 적절하게 구조화해보자. 구조화할 때는 다음 네 가지가 중요하다.
- 목적을 명확히 한다.
- 목적에 따라 합치고 나눈다.
- 목적에 따라 순서를 나열한다.
- 겹치는 내용을 제거한다.
위 원칙을 구조화하면 다음과 같이 정리할 수 있다.
# 초콜릿 브라우니 만드는 방법
## 준비물
* 그릇 3개, 냄비, 베이킹 캔, 베이킹 페이퍼, 오븐
* 버터 100g, 70% 다크 초콜릿 185g, 설탕 185g, 바닐라 에센스 반 티스푼, 계란 두 알, 밀가루 50g, 코코아 분말 35g, 소금 반 티스푼
## 조리 순서
1. 냄비를 준비합니다. 그 후 냄비에 물을 채우고 가스레인지 위에 올립니다.
2. 그릇 하나에 버터 100g과 70% 다크 초콜릿 185g을 넣습니다.
3. 버터와 초콜릿이 들어간 그릇을 냄비 위에 놓고 그릇의 내용물이 녹을 때까지 둡니다. 다 녹으면 그릇을 냄비 밖으로 꺼내서 식힙니다.
4. 버터와 초콜릿이 들어간 그릇이 식히는 동안 또 다른 그릇에 계란 두 알, 설탕 185g, 바닐라 에센스 반 티스푼을 넣고 섞습니다.
5. 버터와 초콜릿이 들어간 그릇이 식었다면 계란이 들어간 그릇에 내용물을 넣고 섞습니다.
6. 마지막으로 남은 그릇에 밀가루 50g, 코코아 분말 35g, 소금 반 티스푼을 넣습니다. 내용을 완전히 섞은 후 체를 통해 버터, 계란, 초콜릿이 들어간 그릇에 넣습니다.
...
먼저 '초콜릿 브라우니 만드는 방법'이라는 목적을 명확히 한 후 준비물과 조리 순서로 영역을 나누었다. 그리고 각 영역에서는 순서를 나열하여 겹치는 내용을 제거하고 빈틈없이 정리했다. 이렇게 구조화하면 글을 읽는 사람이 더 쉽게 이해할 수 있게 된다. 이는 개발에도 마찬가지로 적용할 수 있다. 코드를 작성할 때도 구조화를 잘 하면 코드를 이해하고 유지보수하기 쉬워진다.
방법론
추상적이고 구조적인 사고는 개발자가 문제를 해결하고 프로그램을 만들 때 중요한 역할을 한다. 이제 업무에 적용할 수 있는 방법론으로 어떤 것이 있는지 알아보자.
탑다운과 바텀업
탑다운과 바텀업은 문제 해결에 접근하는 방법론이다. 너무나도 유명하기 때문에 이미 들어본 사람도 많을 것이다. 이 둘은 상호 보완적인 방법론이며 앞서 다룬 추상적, 구조적 사고가 필요하다.
탑다운은 큰 문제를 정의한 후 분해하여 세부적인 문제를 찾아가는 방법론이다. 이러한 사고 방식은 작은 문제들이 아직 미지수거나 리서치가 필요할 때 유용하다. 반면 바텀업은 세부적인 문제를 먼저 해결하며 전체 문제의 답을 찾아가는 방법론이다. 이러한 사고 방식은 작은 문제를 해결하다보면 자연스럽게 큰 문제가 해결된다는 사고방식에 가깝다. 바텀업은 이미 작은 문제를 잘 알고 있고 이를 조합하여 해결할 수 있을 때 유용하다.
어떤 문제에 대해 어떤 방법론을 사용할지는 상황에 따라 다르다. 그러나 앞서 이야기한 것처럼 이 두 가지 방법론은 상호 보완적이므로 하나만 사용하는 것보다 둘 다 사용하는 것이 더 효과적일 수 있다.
모델
개발자는 추상적이고 구조적인 사고를 통해 모델을 만들 수 있다. 그런데 모델이 뭘까? 단순히 데이터로 여겨질 때도 있지만 그것보단 더 많은 의미를 담고있다. 간단하게 요약하면 모델이란 세상을 표현하기 위한 방법이라 할 수 있다. 그리고 이 모델을 만드는 것을 모델링이라고 한다.
모델링은 Classfication, Abstraction, Generalization 세 가지 방법을 이용한다.
먼저 Classfication은 무엇을 어떻게 분류할 것인가라고 볼 수 있다. 앞서 구조화를 다룰 때 나온 묶고 나누는 것에 대한 이야기라 할 수 있다. 분류하는 방법은 Categorizing, Grouping 두 가지가 있다. Categorizing은 비슷한 요소끼리 묶는 것이다. 쇼핑몰 카테고리, 게시판 유형 등이 이에 해당한다. 반면 Grouping은 특정한 기준을 가지고 묶는 것이다. 이 기준은 요소들이 비슷할 필요는 없다. 예를 들어, 나이대별로 묶는 것이 이에 해당한다.
Abstraction은 추상이지만 여기선 추출이라고 해석하자. 요소에 어떤 내용을 추출할 것인지에 대한 것이라 할 수 있다. 추출은 Projection, Layering 두 가지가 있다. Projection은 요소에서 필요한 내용을 추출하는 것이다. 우리가 클래스를 만들거나 함수를 만들 때 역할과 책임에 따라 필요한 내용을 추출하는 것이 이에 해당한다. 반면 Layering은 한 요소를 관점에 따라 여러 층으로 나누는 것이다. 이는 시스템을 여러 레이어로 나누어 관리하는 것이 이에 해당한다.
Generalization은 요소들이 어떤 공톰점이 있는지 찾는 것이다. 이는 요소들이 공통된 특성을 가지고 있을 때 이를 추출하는 것이다.
프레임워크 사고
프레임워크란 Frame과 Work가 합쳐진 단어로 틀에 맞춰 작업하는 것을 말한다. 가둔다는 표현이 안좋게 느껴질 수도 있지만 이를 통해 작업 효율을 개선하거나 다양한 틀에 맞춰 문제를 해석하는 것이 가능하다. 우리는 회고를 위한 KPT, 애자일을 위한 스크럼, 칸반 등 업무 중에 다양한 프레임워크를 접한다. 이러한 프레임워크는 추상적, 구조적 사고를 통해 만드는 것이 가능하다.
개발 세계의 프레임워크도 마찬가지다. 프레임워크는 개발자가 문제를 해결하기 위한 틀을 제공한다. 이를 통해 개발자는 문제를 해결하는데 집중할 수 있고 효율적으로 작업할 수 있다. 그리고 어떤 프레임워크를 사용하냐에 따라 문제에 접근하고 해결하는 방식이 달라진다.
개발에 적용하기
지금까지 추상과 구조적 사고에 대해 알아보았고 어떠한 방법론을 사용할 수 있는지도 알아보았다. 그렇지만 이것 들을 개발에 잘 적용할 수 있을까? 이제 실제로 개발에 적용하는 방법을 알아보자.
도메인 모델링
개발자는 문제를 프로그램을 통해 해결한다. 여기서 문제란 현실 세계의 비즈니스를 의미한다. 개발자는 해결할 문제에 대한 비즈니스 전문 지식을 프로그램으로 옮겨와야 한다. 여기서 해결할 문제에 대한 비즈니스 전문 지식을 도메인이라 부르며 문제에 대한 요구사항과 전문 지식을 프로그램 세계로 표현하는 것을 도메인 모델링이라고 한다.
당연하지만 도메인 모델링은 추상적, 구조적 사고를 통해 이루어진다. 추상적 사고는 도메인의 핵심 개념을 추출하고 구조적 사고는 도메인의 핵심 개념을 구조화하여 나누고 관계를 정의하는 것이다. 이를 통해 개발자는 도메인에 대한 이해를 높일 수 있고 프로그램을 만들 때 도메인에 대한 이해를 바탕으로 프로그램을 만들 수 있다.
이렇게만 말하면 어떻게 해야할지 감이 오지 않을 것이다. 만약 우리가 수업 매칭 서비스를 만들어야 한다고 가정해보자. 이 서비스를 구현하기 위해 다음과 같은 요구사항이 있다.
- 선생님과 학생이 서로 웹을 통해 과외 수업이 매칭될 수 있다.
- 학생은 필요한 수업을 공개 모집할 수 있다.
- 선생님은 학생이 필요한 수업에 적합하다면 참가 신청을 할 수 있다.
- 학생은 모집된 선생님 중 한 명을 선택할 수 있다.
- 매칭되면 결제가 이뤄진다.
위와 같은 요구사항에서 핵심 요소를 찾을 수 있다. 실제로는 여러 도메인 전문가와 이야기하며 진행해야겠지만 여기서는 생략할 것이다.
선생님
과학생
이 서로 웹을 통해 과외수업
이매칭
될 수 있다.- 학생은 필요한
수업을 공개 모집
할 수 있다. - 선생님은 학생이 필요한 수업에 적합하다면
참가 신청
을 할 수 있다. - 학생은 모집된
선생님 중 한 명을 선택
할 수 있다. - 매칭되면
결제
가 이뤄진다.
이렇게 핵심 요소를 추출했다면 도메인 모델링을 시작할 수 있다. 도메인 모델링은 단계적으로 진행해야 하며 바뀔 수 있다는 것을 염두해야 한다.
위 이미지는 가장 추상화된 도메인 모델을 나타낸다. 해당 도메인 모델은 세상의 모든 것을 담을 수 있지만 너무 많이 추상화 되어서 이걸로는 아무것도 알 수 없다. 그렇기 때문에 실제로는 그 누구도 여기서 시작하지는 않을 것이다. 다만, 도메인 모델링은 이렇게 추상화된 모델에서 시작하여 점점 구체화해 나간다는 것을 기억해야 한다.
조금 더 구체화하기 위해 둘로 나누어보자.
그러면 선생님과 학생이 나타나고 둘의 관계를 정의할 수 있다. 하지만 아직 이 둘 사이에 무슨 일이 발생하는지 알 수 없으므로 더 구체화해야 한다.
이번에는 선생님과 학생 사이에 구인과 지원이라는 것이 생겼다. 이를 통해 우리는 학생이 선생님을 모집하고 선생님은 지원서를 넣을 수 있다는 것을 파악할 수 있다. 그럼 요구사항에 맞춰 한 가지만 더 추가해보자.
이번엔 결제라는 도메인이 추가됐다. 이를 통해 우리는 학생이 선생님을 모집하고 선생님이 지원하면 적절히 결제가 이루어진다는 것을 상상할 수 있다. 우리는 이렇게 도메인 모델을 그려가며 현실 세계에서 일어나는 일을 표현하는 것이 가능하다. 그리고 이 도메인 모델을 통해 구체화하는 것도 가능하다. 실제 코드에선 데이터 속성을 추가하고 함수를 통해 행위를 표현할 것이다. 이 과정이 구체화하는 과정이라 할 수 있다.
로직 작성
프로그램은 로직의 집합으로 이루어져 있다. 결국 문제를 해결하기 위해 프로그램이 하는 일은 순차적으로 작성된 로직에 의해 이루어진다.
로직은 Flow, Function, Aspect 세 가지 측면에서 바라볼 수 있다.
먼저 Flow는 어떠한 절차인지를 나타낸다. 예를 들어, 사용자가 로그인을 시도하면 사용자 정보를 확인하고 성공하면 세션을 발급하는 절차를 Flow로 볼 수 있다.
Aspect는 로직이 어떤 관점에서 사용되는지 바라볼 수 있다. 이는 여러 Flow에서 공통적으로 사용되는 로직이 어떤 것인지를 말한다. 예를 들어, 사용자 식별 로직은 사용자 인증이 필요한 모든 Flow에서 사용된다.
마지막으로 Function은 로직을 구성하는 개별 함수가 어떤 것인지를 말한다. 한 로직은 다양한 함수의 조합으로 이루어질 수 있다. 이를 통해 로직을 더 작은 단위로 나누어 관리할 수 있다.
로직을 작성할 때는 이 세 가지 측면을 고려하여 작성하는 것이 중요하다. 이를 통해 로직을 더 쉽게 이해하고 관리할 수 있다.
문법 설탕
문법 설탕(Syntactic Sugar)은 프로그래밍 언어를 간결하게 표현하는 것을 말한다. 따라서 프로그래밍 문법의 세부적인 내용을 숨기기 때문에 추상화한 것이라 볼 수 있다. 만약 DSL을 지원하는 언어라면 직접 문법 설탕을 만드는 것도 가능하다.
표현을 압축시켜주기에 코드 타이핑을 줄이고 가독성을 높이는 것이 가능하지만 합의되지 않은 문법 설탕은 독이 될 수 있다. 예를 들어, 다음 Python 코드가 있다.
# comprehension을 사용하지 않는 경우
total = 0
for i in range(1, 10):
total += i
# comprehension을 사용하는 경우
total = sum(i for i in range(1, 10))
위 코드에서 아래 코드는 comprehension이라는 Python의 문법을 사용하여 더 간결하게 표현되었다. 하지만 이 문법은 다른 언어에선 생소할 수 있고 이해하기 어려울 수 있다. 따라서 만약 Python을 사용하지 않는 조직에서 불가피하게 Python을 사용해야하는 상황이라면 comprehension 문법에 대해 잘 모를 수 있다. 이럴 경우엔 오히려 설탕 문법을 사용하지 않는 것이 더 좋을 수 있다. 항상 추상화의 수준은 협의 후 결정하는 것이 좋다는 것을 명심하자.
리팩터링
리팩터링을 할 때도 앞서 다룬 내용을 유용하게 활용할 수 있다. 리팩터링은 다양한 관점에서 진행 여부를 따져볼 수 있다. 필자는 개인적으로 다음 6가지를 기준으로 삼는다.
리팩터링을 진행하기로 결정했다면 추상화, 구조화, 일반화 세 가지 측면으로 살펴볼 수 있다.
먼저 추상화는 코드 내용을 숨길 것인가? 드러낼 것인가?에 대한 것이다. 예를 들어, 여러 함수를 하나의 함수로 묶거나 라이브러리를 언제든지 교체할 수 있도록 한 번 감쌀 수 있다. 혹은 의도적으로 드러내는 것도 가능하다. 과하게 추상화되어 다른 개발자가 이해하기 어려워하는 로직은 흐름을 파악할 수 있도록 구체적으로 노출하는 것이 좋다.
구조화는 코드 내용을 분리할 것인가? 합칠 것인가?에 대한 것이다. 단일 책임 원칙을 적용하는 것도 가능하며 공통으로 사용되지 않더라도 하나의 함수가 너무 거대한 경우 여러 함수로 분리할 수 있다. 이런 방법은 리팩터링 책 중 함수 추출하기에서도 다루고 있다. 특히 수정 가능성이 높은 로직이라면 의도적으로 분리하는 것이 좋다.
마지막으로 일반화는 코드에서 공통되는 부분을 분리하여 합치거나 의도적으로 중복시키는 것을 말한다. 개발자에게 있어 중복 코드를 분리하는 것은 지극히 당연한 행동이다. 하지만 수정될 가능성이 높은 코드라면 의도적으로 중복 코드를 두는 것도 나쁘지 않다. 왜냐하면 곧 바뀔 것이기 때문이다.
정리하면 추상화, 구조화, 일반화의 수준을 정하는 것이 리팩터링 기술의 핵심이라 할 수 있다.
마치며
사실 앞서 다룬 추상적, 구조적 사고는 이미 우리가 잘 해왔던 것들이라 할 수 있다. 하지만 어렴풋이 아는 것, 직관으로 떠올린 것과 정확한 방법론을 알고 적용하는 것은 엄연히 다르다. 추상적, 구조적 사고는 개발자에게 있어 강력한 무기가 될 수 있으므로 우리가 해결해야 하는 문제를 평소에 어떻게 해결할 것인지 단계별로 접근하는 습관을 길러보자.
그리고 직관에 대해 위험하다고 언급했지만 직관은 경험주의적인 사고라고 할 수 있다. 우리는 직관을 통해 시간을 절약할 수 있기 때문에 이 또한 갈고 닦으면 개발자에게 강력한 무기가 될 수 있다. 다만, 직관만으로 문제를 해결하려고 하지 말고 애매할 때는 추상적, 구조적 사고를 통해 단계 별로 문제를 해결하는 방법을 사용할 줄 아는 것이 중요하다.
Footnotes
-
명사 / a plan of things that are done in order to achieve a specific result ↩