소프트웨어공학
소프트웨어공학(Software Engineering)은 집적 회로 기반 제품 등의 제어를 목적으로 제작하는 모든 종류의 소프트웨어(Software)를 개발하는 방법론을 다루는 학문이다. 컴퓨터공학의 하위 학문으로 분류된다.
소프트웨어공학의 필요성[편집 | 원본 편집]
잘못된 방법론 적용으로 인한 소프트웨어 개발의 실패를 막기 위해서 연구할 필요가 있다.
소프트웨어 개발 프로젝트는 특정한 IT 소프트웨어 제품/서비스의 결과물을 효과적으로 얻기 위한 모든 사업이다. 프로젝트를 제안하는 서류를 작성하고 제출하여 자금 지원들을 받아 프로젝트에 돌입하면 제품을 기획하고 실행하며, 적절한 감시 및 관리를 받아가며 검증하여 프로젝트를 완료하여야 한다. 그런데 이것이 종종 좌초하거나 결과물이 버그투성이로 엉망인 경우가 상당히 많다.
건축공학이나 기계공학 등 다른 분야의 공학을 활용하는 제조업계의 제품 개발 이론과 비교했을 때 소프트웨어 개발 방법에 대한 연구는 그 역사가 몇십 년 수준인데다 현재의 개발 방법론이 30년 전과 크게 다르지 않으며, 이에 따라 소프트웨어 생산성이 사용자들의 서비스에 대한 요구를 따라가지 못하거나, 품질 향상 및 유지보수 난이도가 과도하게 높아지거나, 프로젝트 마일스톤(개발 일정 및 소요 비용 예측)이 부정확한 상황이 자주 발생하는 등 산출물의 품질 제어(Quality Control, QC)가 상대적으로 엉망이다.
이에 따라 소프트웨어 위기(Software Crisis)라는 말까지 등장하여 제대로 된 IT 서비스를 제공하기 위한 방법에 대한 연구가 시급하다. Plan -> Do ->Check ->Action 개념을 반복하는 방법론을 제정하는 PMI의 PMBOK나 소프트웨어 검증 관련 표준인 IEEE1012 같은 가이드라인들이 개발된 것도 그런 이유에 따른다.
종종 건축업계와 소프트웨어업계가 비교되곤 하는데 시공사와 감리사를 나누는 건축업계처럼 '제품 개발 프로세스'뿐만 아니라 '프로젝트 및 품질 관리 프로세스'를 담당하는 기업의 필요성이 높아졌다. 그러나 현재 대한민국에서는 아직 건축업계의 감리 담당 기업 같은 개념이 덜 잡혀서 그런지 종종 발적화라고 욕먹는 저열한 품질의 소프트웨어가 자주 나온다.
중요 개념[편집 | 원본 편집]
소프트웨어 개발 프로세스[편집 | 원본 편집]
전통적인 공정은 다음과 같다. 보통 이를 '폭포수 모델(Waterfall Model)'이라고 부른다.
- 프로젝트 계획(Project Planning): 프로젝트의 관리, 개발, 품질 보증, 통합, 설치, 유지보수, 훈련, 운영 계획을 각각 수립한다. 여기에는 개별 프로젝트의 통합, 작업할 프로세스 범위, 예산을 맞출 수 있는 원가와 필요한 자산의 조달, 품질 관리, 인력 배치 등의 인적자원 관리, 의사소통 방법 명시, 리스크 관리, 이해관계 규명 등이 고려되어야 한다. 보통 이것은 프로젝트 매니저(PM)을 하나 세워서 감독하게 한다.
- 요구사항 분석(Requirement Analysis): 목표를 정확히 확인하는 시스템 요구사항 분석은 보통 기술적 소양과 원활한 의사소통 능력이 있어야 한다. 또한 객관적 관점을 가지고 다양한 고객의 관점을 통합하고 요구사항 명세를 작성하는 혜안이 필요하다. 이에 따라 모델링을 할 때에는 기능별로 연산과 제약조건을 기술하는 기능 관점에 따른 , 시스템의 상태와 변화 원인을 기술하는 동적 관점, 정보를 담은 객체 사이의 관계를 기술하는 정보 관점을 성공적으로 통합하여 데이터가 어떻게 처리되는지 묘사하는 Entity-Relationship Model을 그리는 Class Diagram(객체 다이어그램), Finite State Machine(유한 상태 기계, 상태 다이어그램으로 분류되기도 하다)을 묘사하는 State Diagram(상태 다이어그램), Data Flow Diagram(자료 흐름도, .DFD)을 구성하게 된다.[1] 이에 따라 클래스 다이어그램을 그리는 객체 모델링 -> 상태 다이어그램을 그리는 동적 모델링 -> 자료 흐름도를 그리는 기능 모델링 순서로 요구사항을 정리하게 되는데, 이 방법은 럼바우 분석 기법(Rumbaugh analysis)이라고 불린다.
- 설계(Design): 프로그램의 구조도(structure chart)를 그리기 위해 소프트웨어 디자인 패턴을 도입한다. GoF 기준으로 이들은 생성 패턴, 구조 패턴, 행동 패턴으로 다시 나뉜다.
- 생성 패턴: 자원을 사용하는 코드를 작성하는 패턴이다. 추상 팩토리[2], 빌더[3], 의존성 주입(Dependency Injection)[4], 팩토리 메서드[5], 지연된 초기화(Lazy Initialization)[6], 싱글턴 및 멀티턴 패턴[7], 프로토타입 패턴[8] 등이 있다.
- 구조 패턴: 작성한 코드를 배치하는 패턴이다. 어댑터 패턴[9], 브리지 패턴[10], 컴포지트 패턴[11], 데코레이터 패턴[12], 퍼사드(Facade) 패턴[13], 플라이웨이트(Flyweight) 패턴[14], 프록시 패턴[15], 모듈 패턴[16] 등 다양한 종류가 있다.
- 행동 패턴: 개별 기능의 형태를 명시하는 패턴이다. 책임 연쇄[17], 커맨드 패턴[18], 인터프리터 패턴[19], 반복자 패턴(Iterator를 만들어 리스트를 순회하며 명령을 처리), 중재자 패턴[20], 메멘토 패턴[21], 발행-구독 패턴[22], 상태 패턴[23], 전략 패턴[24], 템플릿 메소드[25], 비지터 패턴[26] 등이 있다.
- 동시성 패턴: 멀티스레딩 등의 병렬화된 작업에서의 동기화를 설계하는 패턴이다. 멀티코어 프로그래밍이 필요해진 21세기에서 중요해지는 디자인 패턴이다.
- 구현(Implementation): 설계한 내용대로 프로그래밍 언어를 활용하여 소프트웨어를 만드는 작업이다.
- 시험(Test): 4가지 전략이 있다.
- 단위 검사에는 코드를 까지 않고 유닛 별로 실행하면서 equivalence partitioning, boundary value analysis, all-pairs test, state transition tables, decision table testing 등을 검사하는 블랙박스 테스트와 코드를 까면서 API 테스트, 코드 커버리지 테스트, 폴트 인젝션, 뮤테이션 테스트, 스태틱 테스트 등을 동반하는 화이트박스 테스트 등이 있다.
- 유닛을 결합하는 통합 검사에는 하향식 통합 검사(메인 모듈을 먼저 테스트하기 위해 종속된 모듈을 해당 모듈이 낼 수 있는 어떤 특정 출력값을 즉시 반환하는 임시 모듈인 Stub 모듈로 대체하여 테스트 후 원래 종속 모듈로 테스트함)와 상향식 통합 검사(개별 모듈을 임시 테스트 코드인 Driver에 결합하여 클러스터를 테스트 후 상위 모듈로 교체하는 것을 반복함)가 있다.
- 소프트웨어의 구현이 제대로 되었는지 검사하는 검증 검사(Validation Test)에는 형상 테스트, 알파 테스트(개발자와 선별된 소규모 사용자들이 함께 고치는 테스트), 베타 테스트(사용자를 공개 모집하여 일부 선정한 뒤 실전처럼 소프트웨어를 굴려 문제를 보고하도록 함) 등이 있다.
- 최종적으로는 소프트웨어를 시스템 위에 직접 올리는 시스템 검사를 한다.
- 유지보수(Maintenance): 수정 보수(Corrective, 잠재 오류를 수정함), 적응 보수(Adaptive, 소프트웨어가 돌아가는 환경의 변화를 반영함), 완전화 보수(Perfective, 새로운 기능 추가 및 성능 개선을 진행), 예방 보수(Prevention, 문제 예방 수단 강구를 위한 분석, 개조, 역공학, 이식을 시도함)가 있다.
만일 폭포수 모델을 따르는 개발 프로세스에서 특정 단계에 문제가 발생한다면 즉시 그 이전 단계를 점검하게 된다. 문제는 후반의 시험/유지보수 단계에 문제가 발생할 경우 이전 단계를 전부 순회하는 데에 엄청난 시간과 노력이 필요하다. 따라서 대체재로 다음 프로세스들이 제시되었다.
- 프로토타입 패러다임(Prototype Paradigm): 기능의 일부만 구현되었으나 그 자체로는 완전한 기능을 하는 프로토타입(원형)을 먼저 만들어 공개한 다음 모든 요구 사항이 구현된 완성품에 도달할 때까지 기능들을 조금씩 추가해나가는 방법이다. 프로토타입 제작 단계에서 제품 기획 단계에서 생각하지 못한 추가적인 기능과 문제점을 발견 가능하고 기획을 더 명료하게 할 수 있으며, 완성품의 가능성과 유용성을 증명할 수 있다는 점에서 제품/서비스 개발 프로세스를 안정화하기 쉽다. 보통 1자형으로 프로세스가 구성되는 폭포수 모델과 달리 프로토타입 패러다임은 원형으로 프로세스가 구성되어 설계와 개발, 평가와 피드백이 순환하는 구조로 되어 있다. 다만 기능을 계속 추가하다가 시제품과 완제품의 제품 기획과 성격이 판이하게 달라질 수 있다는 위험이 있다.
- 나선형 모델(Spiral Model): 폭포수 모델과 프로토타입 패러다임에 위험 분석을 추가해 각 순환마다 위험 평가를 통해 계속 개발할지, 개발을 중단할지 평가하는 체계다. 보통 장기 프로젝트나 국책 IT 사업에 유리한 모델이나 일반적인 상품 기획법으로는 프로젝트 관리의 기준과 평가에 투입할 자원 문제로 쉽지 않은 방법이다.
- 4세대 기법(4th Generation Techniques): CASE 와 정형 명세 언어로 대표되는 기계적 도구의 도움을 받아 명세서로부터 실행 코드를 자동으로 생성할 수 있는 방법이다. 코드 최적화 난이도가 올라가고 불필요한 코드 추가 가능성으로 인해 코드의 유지보수도 어려워지는 등 생성한 결과의 신뢰성 문제로 아직 도입이 빠르지 않으나 생성형 AI가 이 부분에 있어서 엄청난 발전을 선사할 가능성이 제기되었고, Microsoft Copilot 등의 가시적 성과가 나타나고 있다.
- 애자일 방법론(Agile Method): 21세기 들어서는 시점에서 제안된 방법론으로, 프로세스와 도구 못지 않게 고객과의 원활한 커뮤니케이션이 성공적인 소프트웨어 개발에 중요하다는 관점 하에 모든 개발 요구 사항을 추상적이고 큰 규모의 요구 사항에서 구체적이고 자잘한 수많은 세부 사항으로 나눈 다음 고객과 실시간으로 소통하면서 하나씩 iteration으로 구현해나가는 방법이다. 고객의 요구사항이 한 달 안에 구현할 수 있을 정도로 많지 않지만 자주 변화하는 경우에는 민감하게 반응할 수 있으나, 반대로 한 달 정도로는 끝내지 못할 정도로 요구사항이 많은 거대 프로젝트에서는 프로젝트가 엎어질 위험성이 상당히 크다. 그 외에 애자일 방법론은 기술적 진보가 빠른 경우 요구사항 분석 오류나 아키텍처 상의 오류, 테스트 실수, 문서화 오류로 인해 기술적 부채(Technical Debt)가 빠르게 증가하는 경향을 억제하는 데에 도움이 되는 편이다.
- 이때 애자일 방법론 적용과 기술적 부채 억제를 병행하려면 객체 지향 프로그래밍 도입과 비효율적으로 작성된 코드를 교체하는 코드 리팩토링은 필수다. 보통 데이터 타입 등만 미세하게 다른 중복된 구조의 코드, 기능을 불필요하게 몰아 넣어 지나치게 길어진 메서드, 너무 거대해진 클래스 등의 객체를 각각 제네릭 타입 사용 코드, 메소드 분할, 객체 분할 및 상속으로 대체하게 된다.
- 컴포넌트 기반 개발: 프로그램을 기능 꾸러미인 컴포넌트(Component)를 조립하는 방식으로 개발하는 방법이다. 컴포넌트는 그 자체로는 이미 완성되었고 단지 독립적인 실행이 되지 않을 뿐이다. 이를 위해서는 컴포넌트 개발 프로세스와 컴포넌트 조립 프로세스가 분리되어야 한다.
관련 규격[편집 | 원본 편집]
- ISO/IEC 9126: 소프트웨어 품질 평가를 위해 좋은 소프트웨어에 대한 특성 및 척도를 규정한 옛 국제 표준으로, 주 특성 6개(기능성, 신뢰성, 사용성, 효율성, 유지보수, 이식성)에 부특성 21가지를 매칭하였다. 현재는 ISO/IEC 25000에 통합되어 버려졌다.
- ISO/IEC 14598: 소프트웨어 품질 평가의 절차에 관한 옛 표준. 역시 ISO/IEC 25000에 통합되어 버려졌다.
- ISO/IEC 25000: SQuaRE라고도 부르는 소프트웨어 표준 평가 절차. 품질 일반 부문, 품질 모델 부문, 품질 측정 부문, 품질 요구 부문, 품질 평가 부문 5개의 세부 표준으로 나뉜다.
주요 법칙[편집 | 원본 편집]
- 보헴의 법칙: 소프트웨어 개발 후반으로 갈 수록 버그를 발견했을 때 수정에 필요한 노력과 시간, 자원이 증가한다는 법칙.
- 브룩스의 법칙: 소프트웨어 개발이 지체된다고 무작정 인력을 투입하면 교육 비용과 시간이 추가되기에 개발 과정이 더 지체된다는 법칙.
- 파레토의 법칙: 원래는 경제학에서 다루던 법칙(20%의 사람이 80%의 부를 가진다)을 응용한 것으로, 80%의 버그가 20%의 소프트웨어 내 모듈에 집중된다는 법칙이다.
관련 개념[편집 | 원본 편집]
- UML: 객체 지향적 설계를 프로그램 개발 프로세스 설계에 도입할 때 기술하는 언어로 쓴다.
- ↑ 여담으로, Data Flow Diagram과 State Diagram은 컴퓨터 하드웨어 설계(집적 회로)/소프트웨어 설계(컴파일러 등)에 직접 활용하기도 하며, ER 모델을 묘사하는 객체 다이어그램은 데이터베이스 이론에서 주로 쓰던 거다. 개발 프로세스에 대한 연구에 이 개념들을 가져온 건 일종의 응용법이다.
- ↑ 객체(클래스)의 집합을 생성하기 위해 인터페이스 개념으로 객체의 틀만 잡아 놓고 구현은 자식에서 한다는 팩토리 개념을 활용해 단계적으로 집합을 구성
- ↑ 객체의 표현 방법과 생성 방법을 분리하여 부모 객체에 메서드나 기본 변수를 다 정의한 후 자식 객체에서 생성자로 서로 다르게 초기화함
- ↑ 클라이언트에게 무엇을 할 지 일일이 구현하는 대신 클라이언트 객체에서 메서드 명만 호출하기만 하면 서비스 객체가 알아서 하는 부분을 늘리는 방법이다.
- ↑ 부모 객체에서 모든 메서드를 다 구현하고 자식 객체에서는 오버라이드하여 변종 기능을 구현하는 방법. 추상 팩토리와 달리 부모 메서드가 부모 객체에서 이미 구현되어 있을 때 이렇게 부른다.
- ↑ 코드에서 사용하는 변수의 초기화는 그 변수를 사용하는 시점 직전에 초기화한다는 원칙.
- ↑ 객체 내에 그 객체 타입의 인스턴스(초기화된 객체)를 저장하여 한 객체에 하나 또는 객체 개념에 의해 관리되는 여러 개의 인스턴스를 보관하고 Get 메서드로 가져오게 한다. 이러면 단 하나의 인스턴스만 전역으로 선언하고 그 이상의 인스턴스는 전역 인스턴스 내에 저장하게 된다. 변수 내 인스턴스들이 코드 이곳저곳에 흩어지지 않고 한 곳에 모여 구현 단계에서 관련된 코드 관리가 쉽다는 장점이 있으나 역으로 시험 단계에서 객체와 인스턴스 갯수가 일치할 때 쉬워지는 유닛 테스트를 어렵게 하는 단점이 있다.
- ↑ 먼저 모든 객체의 원형을 만든 다음 거기서 모든 객체를 파생시키는 방법. HTML5 및 자바스크립트의 기본 객체인 Object가 프로토타입 패턴 프로그래밍을 강제하는 좋은 예시이다.
- ↑ 클래스 초기화를 할 때 원하는 인터페이스를 선택하여 저장하고 초기화하도록 생성자를 제공
- ↑ 구현부와 추상적 구조 선언을 동등하게 분리하고 클래스에서 인터페이스을 가져와 메서드 초기화를 진행
- ↑ 트리 구조의 다이어그램을 먼저 그려 직관적인 상속 구조를 도식화함
- ↑ 특정 작업을 한다고 클래스 구현 이전에 표식(데코레이터)을 정의에 남겨두는 방법이다.
- ↑ 라이브러리 단위에서 메서드에 대해 선언만 하고 특정 구현이 필요하다고 강제하는 퍼사드 클래스를 정의하여 개별 소프트웨어들의 동작이 라이브러리 동작에 의존하게 되며 발생하는 문제를 줄이는 효과를 준다.
- ↑ 유사한 객체들은 최대한 많은 컴퓨팅 자원을 공유하게 하여 메모리 사용을 줄이는 방법
- ↑ 호출 전용 프록시 객체를 본 객체와 따로 두어 프록시 객체가 조건에 따라 적절한 본 객체를 가리키게 하는 방법. 플라이웨이트 패턴과 결합한 메모리 최적화도 가능하다.
- ↑ 파일 단위로 존재하는 모듈 내에 관련된 변수와 객체, 메타데이터를 모아놓고 소프트웨어 작성 시점에 사전 정의된 모듈의 기능을 불러오는 방법
- ↑ 명령 객체를 가공하는 여러 객체들을 순차적으로 배치하고 연쇄적으로 처리를 시도하는 패턴
- ↑ invoker가 보낸 커맨드 객체를 Receiver가 받아 처리하며 client는 어떤 리시버가 어떤 명령어를 처리할지 지정만 한다
- ↑ Context 코드가 딸린 Expression을 자료구조에 쌓아 놓은 다음 적절한 순회 방법을 골라 처리함
- ↑ 모든 객체가 중재자 객체를 통해서만 상호작용하는 패턴
- ↑ Originator 객체에 CareTaker 객체가 Memento 객체 생성을 요청한 다음 Memento 객체에만 데이터 변경 작업을 한 후 반환하여 Originator를 유지하는 방법. Seed 데이터를 사용하는 난수 생성 기능이 이런 식으로 구현된다.
- ↑ 비동기 통신 알고리즘을 객체 간 상호작용에 적용하여 메세지를 주고 받는 코드 패턴
- ↑ State를 이동하면서 각 State에 지정된 처리를 시행하는 패턴. 소프트웨어 내 Main Loop가 이 패턴을 쓰는 경우가 많다.
- ↑ 서로 다른 알고리즘에 따라 작동하는 객체 내 메소드를 조건에 따라 상호 교체하는 기법
- ↑ 모형이 되는 개별 메소드 내의 서브 메소드의 호출 순서를 정해놓고 객체 별로 서브 메소드의 구현을 다르게 지정할 수 있는 패턴
- ↑ 객체 내에 서로 다른 서브 객체들의 인스턴스들을 두고 생성자에서 어떤 객체로 초기화하냐에 따라 상위 객체의 메소드가 사용하는 서브 객체가 달라짐