오늘은 프로그램과 프로그래밍, 그리고 프로그래밍 패러다임에 대해서 다뤄보도록 하겠습니다.
요즈음에는 다양한 경로를 통해 프로그래밍을 접할 수 있고 그래서 누구나 마음만 먹으면 프로그래밍을 배울 수 있습니다.
이렇게 배운 ‘프로그래밍’이란 기술은 우리가 사용하는 다양한 전자기기, 특히 컴퓨터에서 실행시킬 수 있는 프로그램을 만드는데 사용할 수 있는데요.
오늘날에 프로그래밍이 전공을 불문하고 주목 받는 이유는 아무래도 프로그램이 인류에 가져다주는 높은 생산성 때문이지 않을까 생각이 듭니다.
그렇다면, 여기서 도대체 프로그램은 무엇이고 프로그래밍은 또 무엇인지?
혹시 프로그래밍에 대해서 조금 더 많이 배우신 분이라면 객체지향, 절차지향 프로그래밍이라는 것이 있다는데,
그 둘의 차이는 무엇인지 오늘의 포스팅을 통하여 한 번 정리해보도록 하겠습니다.
개요
프로그램이 무슨 이점을 가져다 주나요?
집 근처에 있는 마트를 방문해서 장을 보러 갔다고 상상해 봅시다.
그런데, 알 수 없는 우연적인 사고로 인해 바코드 리더기를 통해 상품을 찍고 물건을 계산하는 것이 불가능해졌다고 하네요.
계산원 혼자서는 포스기(POS)가 대신 해주던 작업을 모두 소화할 수 없어, 부득이 고객인 여러분들께
내가 고른 상품들의 개별 가격은 얼마인지, 그리고 그 상품들의 총 가격이 얼마인지 직접 계산해줘야 한다고 합니다.
만약 이런 상황이 여러분에게 벌어진다면 어떨까요?
정말 상상만해도 마트 가는 것이 귀찮아지고 장보러 한 번 갔다가 온갖 숫자들에게 시달려 장을 보기도 전에 힘이 빠질 것 같습니다.
이처럼 오늘 저녁에 먹을 식재료를 사러 마트를 방문할 때에도,
마트에서 사용할 돈을 인출하기 위해 ATM기를 이용할 때도,
이제는 마트에 장을 보러 길을 찾기 위해 지도 앱을 사용할 때에도
우리는 언제 어디서나 정말 다양한 ‘프로그램’과 함께 살아가고 있습니다.
물론 프로그램이 없다고 우리가 살아가는 세상이 사라지는 것은 아니겠지만,
오늘날의 사회에서는 프로그램 없이 일상 생활을 살아가는 것은 정말 고단한 일이 되겠죠?
이처럼 반복적이고 단순하나 효율성의 제고가 필요한 다양한 분야에서
프로그램은 인간을 대신하여 ‘귀찮은 작업’을 대신해 처리해주고 있었습니다!
프로그램(Program)은 무엇인가요?
- 컴퓨터에서 실행될 때 특정 작업을 수행하는 일련의 명령어들의 모음
- 특정 문제를 해결하기 위해 처리 방법과 순서를 기술하여 컴퓨터에 입력되는 일련의 명령문 집합체
- 대부분의 프로그램은 실행 중에 사용자의 입력에 반응하도록 구현된 일련의 명령어들로 구현 됨
프로그램의 사전적인 정의를 검색하면 위와 같이 정리할 수 있습니다.
역시나 ‘정의’를 위한 설명 답게 고급스러운 단어들로 잘 압축해서 요약하다보니,
얼핏 읽고서는 쉬이 해석하기 어려운 부분이 있는 것 같습니다.
프로그램은 일련의 명령어들로 모였다고 하네요. 그렇다면, 여기서 명령어란 무엇일까요?
사람에게 어떤 일을 시키려면 우리는 ‘말’이라는 도구를 통해서 ~ 해줘 하면서 부탁을 하면 되잖아요.
하지만, 전기를 에너지로 삼아서 살아가는 컴퓨터에게는 어떤 수단을 통해서 소통해야할까요?
컴퓨터는 ‘명령어’를 통해 사람인 개발자와 소통을 할 수 있습니다.
훌륭한 선대 과학자 및 엔지니어에 의해서 오늘날에 사용되는 다양한 컴퓨터는
개발자라고 하는 소프트웨어 엔지니어들이 ‘명령어’ 를 통해서 특정 작업을 요청하면 수행할 수 있도록 설계되었습니다.
그래서 앞전에 설명했던 프로그램이 인간 대신 수행해주는 다양한 작업들은 이러한 명령어들을 적절히 구성해서
결과적으로 우리의 ‘귀찮고 반복적인 작업’을 대신해주게 되는 것이죠.
그렇다면 프로그래밍(Programming)은 무엇인가요?
컴퓨터 프로그래밍 또는 간단히 프로그래밍 혹은 코딩은 하나 이상의 관련된 추상 알고리즘을 특정한 프로그래밍 언어를 이용해 구체적인 컴퓨터 프로그램으로 구현하는 기술
역시나, 프로그래밍이라는 단어도 정의를 보자니 매우 어려운 단어들로 가득하네요.
프로그래밍을 설명할 수 있는 방법은 수없이 다양할 것 같지만,
오늘은 누구나 쉽게 이해할 수 있도록 간단히 설명하자면 프로그래밍은 ‘프로그램을 만드는 것’이라고 설명할 수 있겠네요.
사실 직관적으로 생각해보더라도 프로그램은 사람 대신 ‘귀찮은 일’을 해주는 도구였으니까,
‘프로그래밍’은 이러한 도구를 만들어 주는 것이겠네~ 라고 생각하실 수도 있을 것 같습니다.
결과부터 말씀드리자면 그러한 설명 또한 알맞은 설명이라고 할 수 있을 것 같습니다.
물론, 실제 프로그래밍을 배우게 되면 단순히 ‘프로그래밍 언어’라고 하는 구체적인 ‘도구’ 뿐만 아니라 다양한 지식과 기술들을 함께 공부해야지 그 시너지 효과가 최대로 발휘될 수 있는데요.
이러한 자세한 내용들은 다음 포스팅 등에서 자세히 살펴보도록 하고 오늘은 개념 위주로 글을 이어가보겠습니다.
프로그래밍 패러다임(Programming Paradigm)이란?
프로그램은 우리가 살아가는데 필요한 ‘귀찮고 반복적인 작업’을 대신해주는 도구라고 설명했습니다.
인류는 지구 상에 등장한 이후로 정말 다양한 도구를 사용하면서 발전 시켜나가면서 삶을 풍요롭게 만들어 갔는데요.
그런데, 이러한 도구는 혼자쓰기는 너무 아깝잖아요.
그래서 누구나 쉽고 간편하게 도구를 사용할 수 있도록 멋지게 손질도 하고 포장도 해서 다른 사람들에게 나눠주려고 결심했습니다.
그런데, 저와 같은 생각을 가진 사람들이 너무 많아서 각 마을마다 도구 장인들이 가지각색의 도구를 시장에 내놓은 상황이네요.
하지만, 자세히 보니 저는 다른 사람들이 사용할 수 있는 ‘망치’를 만들었는데, 다른 마을에서 만든 ‘망치’들은 그 모양도, 크기도, 재질도 가지각색이네요.
한 명의 소비자 입장에서는 이렇게 다양한 제품이 좋을 수도 있겠지만,
한 편으로는 너무 종류가 다양해서 무엇을 골라야할지도 모르겠고
고민하느라 기운을 다 써서 도구를 살 마음이 싹 사라져버리고 말았습니다.
프로그램도 마찬가지로 누가 만드느냐에 따라서 정말 다양한 방식으로 같은 종류의 ‘프로그램’ 즉 ‘도구’를 만들어낼 수 있겠죠.
하지만 이러한 도구가 사용법이나 품질, 그리고 만든 방법이 서로 다르다면? 소비자들을 커다란 혼란에 빠질 것 같습니다.
그래서 이 때 바로 필요한 것이 ‘프로그래밍 패러다임(Programming Paradigm)’인 것이죠.
물론 더욱 면밀하게 정의를 내리고 설명하지만 앞서 설명한 사례가 완벽하지 않을 수도 있지만,
프로그래밍 패러다임은 다양한 성격을 가진 개발자들에게 프로그램을 만들 때 함께 지켜나갈 일종의 ‘약속’을 제시한다는 점에서
사람들이 사용할 프로그램을 비슷하게 해주고, 궁극적으로는 다른 개발자가 ‘프로그램’이라는 도구를 수리해야할 때
쉽게 그 원리를 이해하고 수정할 수 있도록 도와주는 일종의 ‘개발자 전용 가이드북’이라고 할 수 있겠습니다.
프로그래밍 패러다임의 정의는 아래와 같습니다.
프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고 결정하는 역할을 한다.
예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해준다.
프로그래밍 패러다임( Programming Paradigm )은 무엇이 있나요?
프로그램의 역사만큼 프로그래밍의 패러다임도 정말 다양한 형태가 등장해왔는데요.
오늘 설명하는 프로그래밍 패러다임도 시간이 지나면 새로운 패러다임의 등장으로 리뉴얼이 필요할 수 도 있을 것 같습니다.
프로그래밍 패러다임의 계보를 그려보자면 다음과 같이 나누어 그려볼 수 있는데요.
이처럼 프로그래밍의 패러다임, 즉 프로그램을 만드는 일종의 레시피는 위와 같은 종류가 있습니다.
이중에서도 객체지향과 절차지향은 오늘날 프로그래밍에서 정말 많이 다루는 개념이고, 또 중요한 패러다임인데요.
그래서 오늘은 절차지향 프로그래밍, 그리고 객체지향 프로그래밍에 대해서 다뤄보도록 하겠습니다.
1. 절차지향 프로그래밍
절차지향 프로그래밍을 한 마디로 설명하자면, ‘순차적 처리를 위한 명령어의 모음’이라고 설명할 수 있겠습니다.
단순히 특정 작업을 수행하기 위한 명령어의 나열에서 더 나아가 ‘함수’라는 프로그램의 부품들이 서로 유기적으로 연결되어 결과적으로 사람을 대신할 ‘귀찮고 반복적인 작업’을 프로그램을 설계하는 방법입니다.
절차지향 프로그래밍의 대표적인 언어가 c언어이며, 절차지향 프로그래밍 언어가 때로는 명령형 프로그래밍을 대표하는 개념으로 설명되기도 하는데, 면밀히 구분하자면 서로 다른 차이점으로 구분되는 개념입니다.
이러한 절차지향 프로그래밍은 컴퓨터가 ‘명령’이라는 작업을 수행하는 방법과 유사하기 때문에
컴퓨터가 0과 1의 이진수로 이루어진 컴퓨터의 언어로 이해하기 쉽다는 장점이 있고,
그렇기에 다른 언어보다 실행속도, 성능 면에서 굉장히 빠르다는 장점을 갖게 되었습니다.
다만, 절차지향 프로그래밍 언어는 컴퓨터에게 조금 더 친화적이기 때문에 사람인 개발자에게는 이러한 프로그래밍 언어를 이해하고 유지보수하는 일에 어려움을 증가시킬 수 있기 때문에 이러한 단점을 보완하고자 ‘객체지향 프로그래밍’이라는 새로운 패러다임이 등장하는 배경이 됩니다.
물론 절차지향 프로그래밍 언어도 오늘날에도 매우 잘 사용 되고 있는 기술 중 하나입니다.
절차지향 프로그래밍의 특징을 요악하자면 다음과 같습니다.
- 순서대로 일련의 명령어를 나열하여 프로그래밍 한다.
- Function 기반의 프로그래밍이며, 프로시저로 Function 이외에도 Subroutine이 문법적으로 구현되어 있다.
- 절차형 언어의 경우 규모가 커지면 커질수록 함수가 기하급수적으로 늘어난다.
- 함수가 타 프로그램과 문제를 일으킬 수 있는 문제점을 가지고 있다.
- 프로그램과 별개로 데이터 취급이 되므로 완전하지 않고 현실 세계 문제를 프로그램으로 표현하는 데 제약이 있다.
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
객체 지향 프로그래밍(Object-Oriented Programming)이란?
객체지향 프로그래밍을 한 마디로 설명하자면 컴퓨터가 명령어를 수행하는 과정이 ‘객체’ 사이의 상호작용을 통해서 이루어지도록 하는 것이라고 할 수 있습니다.
객체지향 프로그래밍을 설명할 때, ‘객체’란 현실 속에 존재하는 다양한 사물 등을 컴퓨터 프로그램으로 추상화하여 상호작용할 수 있는 주체라는 설명을 많이 사용하는데요.
물론 이러한 설명은 객체지향 프로그래밍을 설명하는 데 매우 적합한 설명이긴 하지만,
오늘은 조금더 쉽게 저만의 해석을 통해서 객체지향 프로그래밍에 대해서 설명해보도록 하겠습니다.
앞서서 컴퓨터 프로그램은 사람을 대신하여 귀찮은 일을 해주는 도구라고 설명했었습니다.
절차지향형 프로그래밍에서 프로그램이란 ‘도구’는 일련의 사용 순서가 정해져 있는 것입니다.
예를 들어 ‘망치’라는 도구를 사용할 때 절차지향 프로그래밍에서는
1. 망치를 든다. 2. 망치 손잡이를 꽉 잡는다 3. 망치를 내려 친다.
이러한 일련의 순서를 통해서 망치를 사용한다면,
객체지향 프로그래밍에서는 ‘망치’라는 도구는 ‘나’ 또는 ‘누군가’가 사용하는데,
그 용도가 못을 박을 때 사용할 수도 있고, 누군가는 길에서 주운 밤 송이를 으깨는데 사용할 수 있는 것이며,
‘망치’라는 도구를 사용하려할 때 누군가가 ‘망치’를 사용하려 한다면 ‘망치’의 소유주에게 망치좀 줄래요? 하고 부탁하고 사용해야하는 다소 복잡하고 번거로운 과정을 통해 도구를 사용하게 됩니다.
이렇게 보니, 객체지향 프로그래밍은 우리가 살아가는 세상과 유사한 점이 더 많이 보이지 않나요?
그래서 일까요 오늘날 다양한 프로그래밍 언어 중 객체지향 프로그래밍 언어 (대표적으로 JAVA)의 인기는 정말 폭발적으로 증가하게 되었고, 사람이 살아가는 세상과 유사, 즉 사람에게 더 친화적인 프로그램이 언어이기 때문에 더 대중적인 프로그래밍 언어가 되었습니다.
물론, 프로그램에서 ‘무조건’ 한 가지 방법만 사용하는 경우는 거의 없고,
우리는 모두 현명한 사람이니까 상황에 따라서 더 ‘적합’한 도구를 적재 적소에 잘 사용하면 될 것 같습니다.
객체지향 프로그래밍(Object-Oriented Programming)을 요약해서 설명하자면 다음과 같습니다.
- 컴퓨터 프로그램을 설계하는 기법 중 하나로, 컴퓨터 프로그램을 현실 세계의 대상과 그 기능을 객체화하여 표현하는 것.
- 모듈화와 객체화를 통해서 유지 보수성이 용이하며, 현실세계를 대상으로 한 설계기법인 만큼 개발자가 코드를 해석하기에 좋아 가독성이 좋은 편이다.
- 객체지향 프로그래밍에서 컴퓨터에 명령을 실행하는 주체는 객체이며 객체들을 통해 명령을 실행하고 객체 간이 상호작용을 통해 프로그램이 실행되도록 한다.
- 객체지향은 추상화, 캡슐화, 상속, 다형성의 네 가지 특성을 갖는다
public class Object {
public static void main(String[] args) {
System.out.println("Hello java");
}
}
객체지향의 설계 원칙
객체지향 프로그래밍 언어는 사람이 사고하는 방식과 유사하기 때문에 접근성이 상대적으로 더 좋은 편이고, 그렇기에 자유도가 굉장히 높게 프로그램을 만들 수 있습니다. 즉, 프로그래밍을 할 때 정말 효율적으로 사용할 수 있다는 것이죠.
하지만, 너무나 높은 자유도는 앞서 설명한 ‘망치’를 시장에 내놓는 상황처럼 모두에게 부정적인 결과를 초래할 수 있기 때문에 궁극적으로는 모든 개발자가 서로 원활하게 소통하며 좋은 프로그램, 즉 도구를 만들 수 있도록 저명한 학자들에 의해 객체지향 프로그래밍 언어는 ‘객체지향 설계 원칙(SOLID)’라는 것을 정립하게 되었습니다.
물론, 이러한 원칙은 프로그램을 작성하는데 자유도를 감소시키거나, 때로는 원칙을 배우기 위해 시간을 투자해야해서 다소 번거로울 수 있습니다.
하지만, 이러한 설계 원칙을 잘 기억하고 고민하면서 프로그램을 만드는 개발자라면,
분명히 다양한 사람들이 쉽고 편안하게 사용할 수 있는 좋은 프로그램, 즉 ‘도구’를 만드는 장인이 될 수 있을 거에요.
객체지향의 5대 설계원칙, SOLID 원칙은 다음과 같습니다.
객체지향 설계 원칙(SOLID)
1. 단일 책임의 원칙 (SRP, Single Responsibility Principle)
- 하나의 클래스는 하나의 목적을 위해서 생성되며, 클래스가 제공하는 모든 서비스는 하나의 책임을 수행하는 데 집중되
어 있어야 한다는 원칙
프로그래밍을 할 때에는, 하나의 프로그램이 정말 다양한 작업을 수행할 수 있도록 구성할 수도 있습니다.
하지만, 하나의 프로그램이 모든 일을 다 해버린다면, 그 프로그램을 수리해야하는 개발자는 무척이나 힘들겠죠.
본디 객체지향 프로그래밍이란 객체들의 상호작용을 통해 컴퓨터에게 명령을 수행하도록 설계되었으므로,
하나의 객체, 즉 컴퓨터에게 명령을 수행하는 ‘주체’가 너무 많은 일을 하지 않도록 적절히 분리해야한다는 원칙입니다.
2. 개방 폐쇄 원칙 (OCP, Open Close Principle)
- 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고 변경에는 닫혀있어야 한다는 원칙
객체지향 프로그래밍에서 서로 다른 두 객체(Class)는 부모-자식과 같은 관계를 가질 수 있습니다.
만약 ‘달리기’라는 기능이 부모에게 존재한다면 이 부모 객체의 ‘자식’에 해당하는 객체에서는 ‘달리기’라는 기능에 추가적으로 다른 기능을 더할 수 있습니다.
하지만, 이때 부모에게 존재하던 ‘달리기’라는 기능이 소리소문 없이 사라져버린다면?
부모만 믿었던 자식의 객체의 입장에서는 정말 당혹스럽겠죠.
이처럼, 객체지향 프로그래밍에서 서로 다른 객체는 부모-자식 과 같은 관계로 서로 관계를 가질 수 있기 때문에,
이러한 점을 고려하여 부모에서 자식으로 향하는 방향에서는 새로운 기능의 추가와 수정이 자유로워도 좋지만,
역은 성립하면 좋지 않다 라는 것이 개방 폐쇄의 원칙이라고 설명할 수 있겠습니다.
3. 리스코프 치환 원칙(Liskov Subtitution Principle)
- 서브 타입(상속 받은 하위 클래스)은 어디서나 자신의 기반 타입(상위 클래스)으로 교체할 수 있어야 한다는 원칙
서로 다른 객체(클래스) A, B, C가 있다고 가정해보겠습니다.
이 객체들은 A와 B 사이에 부모-자식 관계이고, A-C 사이에 부모-자식 관계라고 할 때,
A의 자식은 B만 존재할까요?
이 때는 B도 A의 자식이고, C도 A의 자식이라고 할 수 있습니다.
하지만, B와 C의 관계는 어떨까요? 서로 같은 부모를 공유하고 있지만, 엄밀히 말하면 둘은 서로 다른 객체입니다.
그런데, 이러한 두 객체 모두에게 공통으로 적용되는 것을 만들고자 한다면,
B와 C모두 본인이 가지고 있던 기능을 모두 버리고 부모 였던 A로 모두 회귀할 필요는 없겠죠.
이때 필요한 것이 리스코프 치환 원칙, 같은 부모를 공유하는 형제-자매 객체를 표현할 때에는
자기 자신으로서 표현할 수 있지만, 그들이 상속받은 부모로도 표현할 수 있다라는 것이 리스코프 치환 법칙입니다.
4. 인터페이스 분리의 원칙(ISP, Interface Segregation Principle)
- 한 클래스에는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원칙
- 클라이언트가 사용하지 않는 인터페이스 때문에 영향을 받아서는 안된다는 원칙
바다에서 헤엄치는 로봇을 만들기 위해 프로그램을 만들기로 했다고 가정해보겠습니다.
그런데, 이 바다 로봇은 지난번에 만든 새 로봇을 참조해서 만들기로 결정했습니다. 비슷한 점이 많았던거죠!
하지만, 새 로봇은 바다 로봇에게 필요 없는 ‘날기’라는 기능이 있고,
이 기능은 정작 바다를 헤엄치기 위해 만든 바다 로봇의 기술 ‘헤엄치기’와 간섭이 생겨서 방해를 받는다고 합니다.
그렇다면, 이 프로그램은 잘 설계된 것일까요? 정답은 ‘아니오’입니다.
분명 새 로봇에게는 바다로봇에게 필요한 다양한 기능이 있을텐데,
그러한 기능 외에 사용하지도 않을 기능으로 인해 바다 로봇이 제 역할을 해내지 못한다면 이는 좋은 로봇을 만들 수 없겠죠.
이때 필요한 것이 인터페이스 분리의 원칙, 간단히 요약하자면 필요한 것만 간단히, 요점만 간단히 라는 원칙이라 할 수 있겠습니다.
5. 의존성 역전의 원칙(DIP, Dependency Inversion Principle)
실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하기 만드는 원칙
- 객체에서 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상
위 요소(추상 클래스 또는 인터페이스)로 참조해야 한다는 원칙
멋진 몽키 스패너라는 프로그램을 하나 만들기로 했다고 가정해봅시다.
이 몽키스패너 프로그램에는 지난 번에 만든 ‘망치’라는 프로그램에서 ‘못 뽑기’라는 기능이 필요하다고 합니다.
그런데, 못을 뽑는 기능을 위해 몽키 스패너에서 ‘망치’를 직접 사용하도록 했더니,
세상에 몽키 스패너보다 ‘망치’의 무게 때문에 무게가 너무 무거워서 사용하기 불편해졌습니다.
이때, 이 몽키 스패너는 잘 만들어진 프로그램일까요?
정답은 여러분들도 예상하셨겠지만, ‘아니오’입니다.
만약 ‘망치’라는 프로그램에서 못을 뽑는 ‘부분’만 별도로 추출해낼 수 있어서,
그 기능만을 몽키 스패너에서 장착할 수 있었다면 , 더 가볍고 사용성 좋은 몽키 스패너를 합리적으로 만들 수 있겠죠?
이처럼 소프트웨어 간에 서로 상호작용을 할 때, 필요한 부분만 뽑아내어서 다른 프로그램이 경량화할 수 있도록 돕는다.
이것이 바로 의존성 역전의 원칙이라고 할 수 있겠습니다.
참고 자료
https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8
https://brownbears.tistory.com/407
'CS BASIC > 소프트웨어 설계와 방법론' 카테고리의 다른 글
[CS BASIC] COCOMO, 자료흐름도(DFD), 자료 사전(DD), 코드 설계(Code Design) (1) | 2023.12.25 |
---|---|
[CS BASIC] 모델(Model)과 UML, 다이어그램(Diagram) (1) | 2023.12.24 |
[CS BASIC] 소프트웨어의 디자인 패턴 (1) | 2023.12.23 |
[CS BASIC] 모듈(Module)과 모듈 평가 기준, 소프트웨어 아키텍처 뷰와 프레임워크 (1) | 2023.12.21 |
[CS BASIC] 소프트웨어 비즈니스 계획과 연속성 관리 (0) | 2023.12.20 |