소프트웨어 규모 산정
기능점수방식(Function Point)
< 비용과 노력 추정 매트릭스 >
Economics, Management, IT
< 비용과 노력 추정 매트릭스 >
: 프로젝트의 목표와 요구 사항을 분석하고 이해한다. 이를 위해 이해당사자들과의 의사소통, 현장 조사, 문서 검토 등을 수행하여 요구 사항을 명확히 이해하고 프로젝트의 고유한 요소를 파악한다.
✅ 방법론 선택
: 요구 사항 분석을 토대로 조정이 필요한 영역을 파악하고 이에 따른 방법론을 선택한다. 이는 기준이 되는 표준 방법론에서 일부 요소를 선택하거나 다른 방법론을 조합할 수도 있다.
✅ 프로세스 조정
: 방법론을 조직과 프로젝트에 맞게 조정한다. 이 과정에는 수명주기 단계 추가 또는 제거, 프로세스 세부 조정, 가이드라인과 절차 수정 등이 포함될 수 있다.
✅ 도구와 기술 선택
: 조정된 방법론에 맞는 도구와 기술을 선택한다. 프로젝트에 필요한 특정 기능을 구현하기 위해 특정 개발 도구나 프레임워크를 도입할 수도 있다. 어느 하나를 끝까지 고수할 필요는 없다.
✅ 팀 구성원 역할 및 책임 조정
: 조정된 방법론에 따라 팀 구성원의 역할과 책임을 조정한다. 팀 구성원은 방법론에 맞게 업무를 수행해야 하고 원활한 의사소통과 협업을 진행해야 한다. 팀원들의 역량과 역할에 따라 재조정 또는 새로운 역할을 부여할 수 있다.
✅ 프로젝트 관리 및 모니터링
: Tailoring 된 방법론으로 프로젝트를 관리, 모니터링한다. 이는 일정 관리, 리스크 관리, 품질 관리, 의사소통 관리 등을 포함으로써 프로젝트 진척 상황을 주기적으로 검토하고 필요한 조정을 수행하여 프로젝트 목표 달성을 보장한다.
✅ 지속적 개선
: 고유한 사업적 요구 사항과 제약 조건을 가진 조직은 Tailoring으로 인하여 조직 내 체계를 활성화하고 조직에 맞는 색깔을 찾아 입힐 수 있다. 이에 따라 살아 움직이는 조직으로 보다 유연한 관리가 가능하다.
✔️ 자원 최적화
: 프로젝트 규모와 복잡성에 따라 초기 계획을 지속 관리하고 필요한 자원과 비용을 효과적으로 조절할 수 있다. 즉, 불필요한 자원의 집행을 사전에 확인, 관리할 수 있는 역량을 갖추어 비용과 시간을 절감할 수 있다.
✔️ 프로젝트 성공
설계작업들이 끝나면 시작되는 소프트웨어 프로그래밍은 요구사항에 대한 실질적인 구현행위이다. 특히 분리하여 구현할 수 있는 작은 단위로 나눠 작업을 한다. 프로그래밍의 수행이 상세 설계나 사용자 지침에 기술된 내용과 일치되도록 작업하여야 한다. 이 작업에서 가장 중요한 것은 표준을 정하고 준수하고 이에 따라 정확하게 작성하는 것이다. 소프트웨어의 품질은 결국 이를 얼마나 잘 수행하여 원시 코드에 반영하느냐이다. 따라서 프로그래밍에서는 소프트웨어의 기능구현이 급급한 것이 아니라 요구되는 품질에 얼마나 부합하도록 작업하는가에 대한 관심과 끊임없는 노력이 매우 중요하다.
※ 코딩과 프로그래밍?
이 용어는 모두 컴퓨터와 상호 작용하고 컴퓨터 시스템을 생성하고 유지 관리하는 데 중요한 기술로 종종 같은 의미로 사용되지만, 프로그래밍 과정 중 코드를 작성하는 구체적인 단계를 가리키며, 프로그래밍은 문제 해결을 위한 전체적인 과정을 의미하기에 사실은 서로 다르다.
앞서 용어 정의와 같이 코딩은 프로그래밍 작업이다. 이러한 구현단계의 목표는 설계 명세에 나타난 대로 사용자 요구를 만족할 수 있도록 프로그래밍을 하는 것이다. 이렇게 하기 위해서는 코딩 단계에서는 전 단계의 문서들을 잘 참조하여야 한다. 특히 무엇보다도 오류가 적은 품질 좋은 프로그램을 작성하는데 그 목표가 있다. 신속성도 중요하지만 견고한 코드를 만들어내는 것에는 많은 연습과 경험이 필요하다. 이를 위하여 수많은 원리와 가이드를 만들고 잘 참고해야 한다.
일반적인 객체지향 코딩에는 다음과 같은 단계가 있다.
이러한 작업을 진행하면서 오류는 프로그래머가 가장 신경 써야 할 부분 중 하나다. 개발과정에서는 특히 많은 시간이 오류를 찾고 해결하는 데 쓰이는데 일정 부분 이상은 흔히 발견되는 오류로 이를 알고 접근한다면 많은 부분의 비용을 절감할 수 있다. 아래는 여러 오류 중 중요한 몇 가지의 예이다.
구조적 프로그래밍, 이를 위해서 많이 사용하는 제어구조가 있는데 순차, 선택, 반복이다. 이 또한 제대로 된 흐름제어 기준을 정해놔야 한다. 무조건적인 제어의 흐름을 막고 알고리즘을 명확하게 구현할 수 있는 가이드를 제시하고 공유한다. 물론 근래의 프로그래밍 언어들은 이를 고려하여 여러 명령어나 제약을 두고 있으며 필요하다면 비구조적 문형으로 개발을 할 수도 있다.
또한 모든 프로그램에는 항상 도메인에 관한 정보를 다루기 때문에 자료구조가 항시 존재한다. 그래서 특정 정보에 대해서는 정해진 오퍼레이션만 적용된다. 즉 문제 도메인에 있는 아주 작은 정보가 제한된 방법으로만 사용된다는 것이다. 이를 앞서 다뤄본 정보은닉의 원리 적용이다. 정보은닉이란 모듈 사이의 결합을 줄이고 시스템의 유지보수를 쉽게 만드는 장점을 가지고 있고 데이터를 관리하려는 관점과 데이터를 사용하는 관점을 분리할 수 있는 것이다.
프로그래밍 스타일이다. 같은 작업을 위하여 여러 사람이 작성한 프로그램들은 문장의 패턴이나 그 구성 등 여러 면에서 다른 스타일을 보인다. 각자의 개성이다. 하지만 이를 방치할 경우 프로젝트는 원하는 목표를 달성하기 어렵다. 그래서 가르치고 학습할 수 있는 공통된 스타일을 만들어야 한다. 물론 여기에서도 기준을 구하기 어려울 수도 있지만 가장 근본은 간결하고 읽기 쉬워야 한다는 것이다. 이를 기준 삼아 코딩 스타일을 만든다.
무엇이든지 복잡한 생각이나 아이디어를 간결하고 정확하게 표현하려면 여러 방법이 있지만 이를 통해서 의사소통의 오류를 줄이는 것은 소프트웨어 개발 프로젝트에선 필수사항이다. 정확한 의사소통은 먼저 표현하는 의미를 잘 정의해야 하고 대상을 표현하는 데 적합하고 모든 이해당사자가 이해하기 쉬워야 한다. 그래서 표준이나 규격이 필요한 것이고 이것이 객체지향에서는 UML(Unified Modeling Language)을 사용하는 것이다.
UML은 OMT(Object Modeling Technique)와 Booch, OOSE(Obect Oriented Software Engineering)의 통합으로 만들어진 표현 방법으로 객체지향 분석설계기법으로 매우 유용하고 시스템 개발에선 크게 기능적 모형, 객체 모형 및 동적 모형으로 구성된다. 이 중 중요한 다이어그램이 5가지 있는데 Use Case, Class, Sequence, State, Activity가 그것이다.
객체지향 모델링에는 크게 네 가지 관계, 연관, 전체/부분, 상속, 사용 관계가 그것인데 이들의 각각의 관계는 클래스 또는 객체가 관계를 맺고 있는 특별한 유형을 의미한다. 이에 각각 살펴보면 다음과 같다.
객체지향 설계는 소프트웨어 시스템을 개발하기 위한 계획과 구조를 만드는 과정으로 시스템 설계와 객체 설계는 객체지향 개발의 두 단계로서 각각 전체적인 아키텍처와 개별 객체의 세부 사항을 다룬다. 이 둘은 서로 밀접하게 연관되어 있으며 시스템 설계 단계에서 결정된 아키텍처와 모듈화는 객체 설계 단계에서 각 객체의 역할과 협력을 결정하는 데 사용된다. 따라서 객체지향 설계는 시스템의 기능, 구조, 동작을 종합적으로 고려하여 효율적이고 확장 가능한 소프트웨어 시스템을 개발하는 데 필요하다.
객체지향 설계작업은 방법론이 있지만 쉬운 작업은 아니다. 이를 위해선 많은 경험과 인사이트가 필요하다. 이에 객체 설계의 경험을 토대로 한 디자인패턴 개념이 등장한다. 디자인패턴은 프로그램 개발에 자주 등장하는 문제를 기술하고 같은 작업을 반복하여 설계하지 않고 여러 번 반복하여 사용할 수 있는 문제에 대한 솔루션을 기술한 것이다. 다만 패턴에서 기술된 솔루션이 특정한 구현을 나타낸 것은 아니며 여러 상황에서 적용될 수 있는 템플릿 성격이라고 보는 것이 맞다. 그래서 문제에 대한 설계를 추상적으로 표현하여 문제를 해결하려는 요소들을 일반화하고 잘 정리한 것이다.
이러한 디자인패턴은 다음과 같은 요소로 구성된다.
패턴을 분류하는 기준은 여러 가지나 보통 23개의 세 가지 유형을 가진 Gamma 분류체계가 일반적인데 아래 간략히 소개한다.
소프트웨어 개발에서 객체지향 프로그램은 독립 객체들의 묶음이다. 따라 분석과 설계 단계에도 프로그램 모듈 단위인 객체의 정적인 구조와 동적인 변화를 미리 고려해야 한다. 분석 단계에서는 사용자 관점에서 여러 사용 사례를 찾아보고 클래스들의 정적인 관계와 객체들의 인터랙션을 찾아낸다. 설계 단계에선 클래스들의 묶음으로 시스템 구조를 정의하고 클래스 내부를 설계한다. 이러한 분석과 설계과정은 순차적 과정이 아니다. 이는 반복과 점증적 개발 프로세스를 사용하고 있으며 이를 통해 반복적 사이클을 거치면서 점차 확장되고 완성되어 가는 것이다.
객체지향 분석과 설계과정은 누구나 공통으로 사용하는 프로세스는 없다. 물론 여러 가지 제안 프로세스들이 있으나 모두 상이하다. 이에 모델링은 매우 중요하게 대두된다. 모델은 프로젝트에 참여하는 모든 사람이 목표로 하는 소프트웨어를 잘 이해할 수 있게 하며 이를 사용함으로써 시간과 비용을 절약할 수 있다. 객체지향 분석과 설계에서는 보통 3가지 모델을 사용한다. 이는 기능 모델(Use case Diagram), 객체모델(Class Diagram), 동적모델(State Diagram/Sequence Diagram)이 그것이다.
객체지향을 통해 시스템 개발 시 가장 먼저 할 일은 요구사항을 추출하는 것으로 여기에 Use Case가 사용된다. Use Case는 시스템이 수행할 것으로 기대되는 기능을 말하는데 이는 사용자 또는 외부 시스템이나 기타 요소들이 시스템과 상호작용하는 다이얼로그를 모델링한 것이다. 모든 Use Case는 외부 엔티티들이 시스템과 어떻게 상호작용하는지 가능한 시나리오를 나타내는 것으로 이를 모으면 전체 시스템의 완전한 모습을 보여주는 것이다. 그래서 Use Case는 사용자나 시스템 설계자, 테스터 및 개발자 간 의사소통에 매우 유용하다.
이러한 Use Case는 문제 정의에서 사용사례로 구성된 시스템 명세로 매핑하는 작업이다. Use Case를 작성하고 관계를 찾는 것은 시스템의 요구사항을 명확하게 정의하고 팀 간 의사소통을 원활하게 하며 개발 과정을 체계화하는 것으로 이를 통해 시스템의 기능을 완전하게 이해하고 효과적으로 구현할 수 있다. 이에 대한 작업 과정은 다음과 같다.
Use case를 작성하고 도메인 분석이 어느 정도 마무리되고 나면 객체를 찾고 관계를 정의하는 작업을 시작하게 된다. 이를 객체 모델링이라 부르며, 클래스를 발견하고 난 후 클래스들의 상호작용이나 클래스의 상태 변화 등 시스템 내부의 동작을 구축하는 것을 동적 모델링이라 부르며 UML에서는 Sequence diagram, State diagram, Activity diagram으로 작업한다. 이들은 시스템의 구조와 행동을 모두 고려하여 전체적인 시스템 설계를 돕는 역할을 한다.
소프트웨어 공학 분야에서 객체지향 기술은 오래전부터 여러 분야에서 연구가 되어 왔던 아이템이다. 특히 1990년대부터 소프트웨어 분야에서는 매우 중요한 기술로 다뤄지며 생산성에 초점을 맞춘 프로그래밍 분야에서는 이제는 빼놓을 수 없는 핵심기술이다. 이는 소프트웨어의 발전이 하드웨어를 따라가지 못한다는 ‘소프트웨어 위기’를 타개할 수 있는 방법으로서 이제는 개발 주류가 된 객체지향은 특수화되고 적절하게 변경이 되며 소프트웨어 부품을 보다 체계적으로 다시 사용할 수 있는 인프라가 되었다.
객체지향의 특징은 무엇보다도 절차적인 프로그램과의 차이점을 보면 명확해진다.
🚩 모형의 적합성
복잡해지는 문제를 잘 모델링하기 위해서는 부분적인 자세함도 필요하지만 적절하게 자세한 것을 생략할 수 있는 추상화도 가능하여야 한다. 이는 객체지향에서 정보은닉과 메시지 교환이라는 개념을 활용하여 작업이 분산되고 감춰진다. 함수의 집합으로 구성된 절차적인 프로그래밍과 달리 객체지향 프로그램은 내부가 감춰진 객체들의 상호작용으로 구성된다. 그래서 자료를 따로 가진 객체들이 필요할 때만 협력한다. 더불어 개념이나 실체를 이산적인 객체로 모델링하여 독립된 객체로서 구현시킨다.
🚩 재사용 용이
재사용이 쉽다. 객체는 열림 특성과 닫힘 특성을 모두 갖추고 있고 재사용 부품으로서 그 역할을 한다. 닫힘은 변하지 않는 부분은 손대지 않고 그대로 다시 사용하는 것을 말하며 열림은 그 반대로 이를 변경 또는 확장하여 사용할 수 있는 것이다. 이를 가능하게 하는 개념은 바로 상속과 다형성이다. 추상화 클래스를 정의하고 파라미터를 용도에 맞게 사용할 수도 있고 캡슐화된 클래스를 그대로 쓰면서 필요에 따라 확장도 가능한 것이다.
🚩 Time to market
소프트웨어의 효율성 외에도 품질이나 유지보수 용이성, 시장 출하 시점도 소프트웨어 설계에 있어 매우 주요한 설계 기준이다. 시장에 처음 출시된 제품이 나중에 나온 제품보다 기능이 떨어지더라도 시장을 선점하고 점유한 상태를 무시할 수 없기 때문이다. 객체지향은 재사용이 용이하고 재설계가 필요 없기 때문에 개발기간을 단축할 수 있고 필요시 원하는 시점에 맞춰 제품 출하를 가능하게끔 하는 기술이다.
🚩 설계와 프로그래밍 매핑
절차적 방법에 비해 설계단계에서 프로그래밍으로의 전환이 매우 순조롭다. 즉 쉽게 매핑될 수 있다는 것인데 프로그램의 기초 단위인 객체나 클래스 개념을 말하는 것이다. 물론 코딩 자체는 다소 어려울 수 있더라도 분석 설계 단계에서 부분적인 코딩이 자유로운 객체지향은 모델링 기초 개념이 동일하여 이 또한 개발기간을 단축할 수 있는 결과를 가져올 수 있다.
🚩 객체(Object)
객체는 현실 세계의 개체나 개념을 소프트웨어적으로 모델링한 것이다. 즉 필요한 자료구조와 이에 수행되는 함수들을 가진 하나의 소프트웨어 모듈로 볼 수 있다. 이는 자료구조를 갖고 상태를 가진다는 것인데 데이터와 해당 데이터를 처리하는 메소드(함수)를 함께 포함한다. 예로, 자동차 객체는 브랜드, 모델, 속도 등의 데이터와 가속, 정지와 같은 동작을 수행하는 메소드로 구성된다. 결국 객체는 상태와 행위, 정체성을 갖는, 비슷한 객체의 구조와 행동이 공통 클래스로 선언된다.
🚩 클래스(Class)
클래스는 객체를 생성하기 위한 템플릿이며 객체의 구조와 행동을 정의한다. 즉 객체의 타입이 클래스이다. 클래스는 속성(변수)과 메소드(함수)로 구성되며 객체를 생성할 때 이러한 속성과 메소드가 해당 객체에 포함된다. 자동차 클래스는 자동차 객체들이 가져야 할 속성과 메소드를 정의하는 것이 그 예이며 클래스에 속하는 객체를 이 클래스의 인스턴스라고 한다. 이런 클래스를 정의하기 위해서는 클래스가 가지는 속성을 도출하여야 한다. 다시 말해 클래스는 객체들이 갖는 속성과 적용 연산을 정의하고 있는 템플릿이다.
🚩 캡슐화(Encapsulation)
캡슐화는 객체의 상태(속성)와 행동(메소드)을 하나의 단위로 묶는 것이다. 객체는 외부에서 직접 접근하는 것을 제한하고 객체의 내부 동작을 숨기는 것으로 정보은닉을 지원하는데 이를 통해 객체의 내부 구현 세부 사항을 감추고 외부에서는 필요한 인터페이스를 통해 상호 작용할 수 있다. 캡슐화는 설계나 분석 단계에서 주어진 문제를 간단히 처리할 수 있는 추상화의 수단이 된다.
🚩 상속(Inheritance)
상속은 클래스 간의 계층 구조를 만들어 기존 클래스의 속성과 메소드를 다른 클래스가 상속받아 재사용할 수 있게 해준다. 부모 클래스(상위 클래스 또는 기본 클래스)의 특징을 자식 클래스(하위 클래스 또는 파생 클래스)가 물려받아 확장하거나 수정할 수 있다. 이를 통해 코드 재사용성과 계층적 구조를 갖는 클래스들을 구성할 수 있다. 이런 상속은 슈퍼 클래스, 서브 클래스로 클래스를 세분화하며 객체가 갖는 특유의 속성들을 추가할 수 있다. 참고로 두 개 이상의 슈퍼 클래스에서 상속받는 것을 복수 상속이라 한다.
🚩 다형성(Polymorphism)
다형성은 같은 이름의 메소드를 다양한 객체에서 호출할 때 다른 동작을 수행하도록 하는 개념으로 여러 가지 형태를 가지고 있다는 말이다. 이는 상속과 연결되어 있으며, 자식 클래스는 부모 클래스의 메소드(특정한 클래스를 위해서 오퍼레이션을 구현한 것)를 오버 라이딩(재정의)하여 자신에 맞게 수정할 수 있다. 다형성을 통해 객체 간의 유연하고 일관된 상호작용을 가능하게 한다. 다형성은 현재 코드를 변경하지 않고 새로운 클래스를 쉽게 추가할 수 있다.
객체지향 프로세스의 단계들은 선형적인 순서로 진행되는 것이 아니라 실제 개발 과정에서는 반복되거나 중첩되는 경우가 많다. 이는 소프트웨어 개발의 복잡성을 다루기 위해 계획, 설계, 구현, 검증, 유지보수 등을 체계적으로 수행하여 효율적이고 유지보수가 가능한 소프트웨어를 만들기 위한 접근법으로 큰 틀에서는 일반적인 소프트웨어 개발 프로세스와 동일하다.
✔️ 요구사항 분석
프로젝트의 목적과 필요한 기능을 파악하고 문서화한다. 이 단계에서는 사용자와 고객의 요구사항을 파악하고 이를 객체와 클래스로 변환하여 소프트웨어의 주요 기능과 특성을 정의한다. 주요 과정으로는 요구추출 단계에서는 액터, 시나리오, 여러 사례들을 찾고 구체화하며 여러 사례 간의 관계를 바탕으로 비기능적 요구를 명확화한다. 이후 요구분석을 통해 분석모형을 위한 객체, 객체 간 모형화, 연관관계, 속성 등을 찾고 정리한다.
✔️ 시스템 설계
요구사항을 바탕으로 시스템의 구조와 아키텍처를 설계한다. 클래스와 객체의 관계, 데이터베이스 구조, 인터페이스 등을 정의하고 시스템의 전체적인 동작 방식을 계획한다. 즉 설계목표를 정의하고 서브 시스템을 파악하고 여러 저장소를 설계하며 이를 패키지 다이어그램으로 정리한다.
✔️ 클래스 설계
시스템 설계를 바탕으로 클래스와 객체의 세부 사항을 설계한다. 클래스의 속성과 메소드를 식별하고 클래스 간의 관계를 설정하며 적절한 상속 구조를 설계한다. 객체를 정의하고 부품의 선택과 최적화를 통해 설계를 진행하며 상세화된 클래스 다이어그램을 작성한다.
✔️ 구현 및 테스트
클래스 설계를 기반으로 실제 코드를 작성한다. 각 클래스의 메소드를 구현, 데이터를 처리하며 객체들을 생성하고 관리한다. 이 단계에서는 객체지향 프로그래밍 언어를 사용하여 클래스와 메소드를 코드로 표현한다. 그리고 작성한 코드를 검증하여 시스템이 요구사항을 충족하는지 확인한다. 단위 테스트, 통합 테스트, 시스템 테스트 등 다양한 수준의 테스트를 수행하여 버그를 찾고 수정한다.
✔️ 배포 및 유지보수
개발한 소프트웨어를 배포하고, 사용자가 실제로 사용하면서 발생하는 문제를 해결하고 기능을 추가 또는 개선한다. 유지보수 단계에서는 시스템의 변경이나 업데이트가 필요한 경우 이를 수행한다.
✔️ 문서화, 평가와 개선
프로세스의 각 단계에서 만든 문서들을 정리하고 보완하여 개발된 소프트웨어의 설명서를 작성한다. 프로젝트가 완료된 후에는 전체 프로세스를 돌아보고 개발한 시스템의 성능과 사용성을 평가한다. 이를 통해 개선점을 발견하고 다음 프로젝트에 반영하기 위한 교훈(lessons learned)을 얻는다.
프로그램 설계(Programming Design)는 소프트웨어 공학의 개발 과정 중 하나로, 시스템의 각 모듈 안에서 일어나는 일들과 모듈 안에서의 자료구조에 관하여 설계하는 단계이다. 각 모듈 안에 사용될 알고리즘을 고안하는데 이는 소프트웨어 개발에서 설계 도면과 같은 역할을 한다. 이런 프로그램 설계는 시스템 구조설계와 분리하여 별도의 단계로 추진하기도 하지만 때로는 병행 진행하기도 한다. 프로그램 설계는 효율적이고 유지보수가 가능한 소프트웨어 시스템을 만들기 위해 필수적인 활동이며 요구사항 분석을 바탕으로 시스템의 목표를 이루기 위한 적절한 아키텍처와 구조를 결정하는 것으로서 잘 설계된 소프트웨어 시스템은 확장성, 유지보수성, 재사용성 등을 갖추어 복잡한 프로젝트의 성공을 보장하는 데 도움을 준다. 이에 프로그램 설계는 크게 두 가지 측면에서 이루어진다.
구조적 설계에 이어 상세설계 단계는 모듈을 하나씩 다룬다. 각 모듈을 구현하기 위한 알고리즘을 선택하고 기술하는 것이다. 시스템 구조설계가 잘 이루어진 상태라면 각 모듈 안의 설계는 어렵지 않다. 각 모듈의 역할이 분명하고 모듈 사이의 결합도가 낮게 구성되기 기 때문이다. 설계의 원리들을 따 잘 설계하였다면 모듈을 어떻게 구현할 것인가를 결정하는 일은 알고리즘 선택으로 국한된다.
알고리즘 선택의 3가지 원칙은 다음과 같다.
설계단계에서 사용되는 모든 표현법은 매우 중요하다. 이는 의도하는 바를 정확하게 나타낼 수 있고 의사소통을 원활하게 해준다. 모듈 안 알고리즘을 기술하는 방법도 여러 가지가 있다.
사용자 인터페이스 설계(User Interface Design)는 소프트웨어 공학에서 사용자와 소프트웨어 시스템 간의 상호작용을 디자인하는 과정으로 사용자가 시스템과 소통하는 방식을 결정하고 설계한다. 사용자 인터페이스는 시스템의 기능을 사용자가 이해하고 조작할 수 있도록 돕는 중요한 요소이며 사용자 만족을 전제로 한다. 이에 사용자 인터페이스에 대한 평가 요소가 중요하며 특히 배우기 쉬워야 하고 반응속도가 좋고 사용 중 오류의 빈도가 낮아야 하며 사용자를 만족시키고 사용법을 계속 유지해야 하는 점이 강조된다. 이를 모두 만족시키는 것이 이상적이기는 하나 중요한 사항을 선택적으로 충족시키는 경우도 많다. 이에 사용자 요구사항에 대한 우선순위를 고려하여 높은 우선순위부터 사용자 만족을 충족시키는 형태로 개발해야 한다. 이에 사용자 인터페이스 설계는 다음과 같은 목표를 갖는다.
좋은 사용자 인터페이스 설계는 소프트웨어 제품의 성공과 사용자 만족도에 큰 영향을 미치며 사용자들의 요구와 행동을 고려하여 개발되어야 하는데 보통은 다음과 같은 단계로 진행된다.
소프트웨어 공학에서 구조적 설계는 소프트웨어 시스템을 개발할 때 전체 시스템의 구조와 구성 요소 간의 상호작용을 계획하고 설계하는 단계이다. 이는 소프트웨어 개발 프로세스의 중요한 부분으로 시스템의 복잡성을 다루며 유지보수할 수 있고 확장할 수 있는 소프트웨어 시스템을 만들기 위한 첫 번째 큰 단계 중 하나다. 이러한 구조적 설계는 앞서와 같이 모듈화와 추상화, 모듈 간 인터페이스 설계, 데이터 구조 설계, 제어구조 설계, 자료구조 설계 및 모듈의 응집도와 결합도 등으로 구성되어 있다. 특히 구조적 프로그래밍을 통해 GOTO 문을 최소화하고 대신 조건문과 반복문 등을 사용하여 프로그램 흐름을 명확하게 구조화한다. 이는 코드의 가독성과 디버깅 효율을 높이는 좋은 방법이다. 또한 일반적인 소프트웨어 설계 문제에 대한 해결책으로 사용되는 디자인 패턴을 활용하여 설계의 일관성과 재사용성을 향상한다.
구조적 설계는 소프트웨어의 크기와 복잡성을 관리하고 효율적인 개발 및 유지보수를 가능하게 해주는 중요 단계다. 이를 통해 개발자는 요구사항을 충족하면서도 확장 가능하고 품질 좋은 소프트웨어 시스템을 개발할 수 있다.
구조적 분석은 설계 결과를 구조도로 나타낸다. 구조도는 시스템의 모든 모듈을 사각형의 박스로 표현하며 모듈의 호출 관계를 화살표로 나타내는 계층적 트리 형식의 표이다. 결국 소프트웨어 구조도는 시스템을 어떤 모듈들로 나누었는가를 명시적으로 나타내며 모듈들의 계층구조와 모듈 간 입출력, 인터페이스, 기능을 나타내는 이름들을 보여준다. 이러한 시스템 구조는 전체적으로 치우침 없는 균형을 이뤄야 한다. 그렇지 못한 경우 처리에 병목현상이 발생한다. 균형은 구조도의 폭과 깊이, 입출력 또는 처리 편중, 영향 범위 등에 의해서 결정된다. 시스템 구조도가 불균형인 경우 모듈의 역할이나 규모를 다시 나누어 균형을 이루도록 해야 한다.
구조의 깊이는 시스템 구조도의 레벨 수다. 깊이는 모듈 사이에서 호출하고 반환하는 과정이 길게 이어진다는 뜻이다. 따라서 깊이가 깊어지면 최하위 모듈까지 통신에 대한 부하가 늘어난다. 또한 구조의 넓이는 상위 모듈이 호출하는 모듈의 개수이다. 이를 보통 Fan-out이라 하며 한 모듈에서 호출하는 하위 모듈이 너무 많으면 호출하는 모듈이 제어할 때 병목 현상이 생길 수 있다. 제어의 범위를 너무 크지 않게 하는 것이 운용의 묘를 살리는 것이며 이 또한 매직넘버 7 내외를 유지하는 것이 좋고 이것이 Fan-out 수다. 여기서 호출하는 모듈수를 줄이려면 중간층의 모듈을 몇 개 두어 중간 모듈이 하위 모듈을 호출하도록 한다. 또한 하위 모듈 중 그룹화할 수 있는 모듈이 있거나 다른 모듈 등이 있다면 이를 분리한다.
구조적 설계는 분석 단계에서 생성된 자료 흐름도를 활용하여 소프트웨어 구조를 추출한다. 이에 두 가지 방법으로서 변환분석과 처리분석으로 구조를 추출한다.
흔히 스타일이라고 하면 설계와 시공을 위한 큰 밑그림을 그리는데 여기서 일관된 모양과 조화를 뜻한다. 이 스타일을 소프트웨어 구조에서도 사용할 수 있다. 소프트웨어 시스템이 복잡해지면서 시스템의 구조 문제는 더욱 중요해졌는데 왜냐면 일단 개발이 시작되면 이후 잘못된 부분을 바로잡기가 쉬지 않기 때문이다. 이런 문제의 중요성을 인식하여 소프트웨어 구조 개념이 나타났다. 소프트웨어 구조는 시스템 분할, 전체 제어 흐름, 오류처리 방침, 서브 시스템 간 통신 프로토콜을 포함한다. 이에 몇 가지 주요 스타일이 무엇이 있는지 살펴볼 필요가 있다.
무엇을 개발할 것인가에 대한 요구분석이 끝나면 다음 단계로서 소프트웨어 내부 구조와 자료구조를 설계해야 하는 일이 있다. 소프트웨어는 그 품질을 인정받기 위해서는 요구분석 산출물에 기술된 기능을 잘 수행하도록 설계되어야 하는 중요성을 가지고 있다. 소프트웨어 구조는 소프트웨어 시스템의 골격과 조직이며 이를 설계하는 것은 모든 구성요소인 모듈들을 정의하고 모듈 사이 인터페이스를 정하는 것이다. 이것들이 제대로 설계되지 않는다면 이후 개발, 테스트 및 유지보수 등에 있어서 큰 어려움이 뒤따른다고 봐야 한다. 소프트웨어 설계 작업의 결과는 여러 원리를 토대로 다양한 결과물을 만들어낸다. 구조와 알고리즘, 프로그래밍, 데이터베이스 등 요구분석서와 설계서 사이 설계단계는 구현으로 가기 위한 징검다리이다.
이러한 설계는 창조적인 작업이다. 하지만 원리적 접근이나 체계적 개념이 없는 것은 아니다. 다른 창작 작업도 일정한 틀과 원리가 적용된다. 소프트웨어를 설계하는 과정에서도 설계 기법과 많은 도구가 쓰인다. 특정한 설계기법의 선택이 품질 좋은 소프트웨어를 만드는 데 필수 요건은 아니다. 다만 설계 기법을 잘 적용하면 소프트웨어 설계는 더 개선된다.
앞선 요구분석서는 시스템 문제를 해결하기 위한 기술서이다. 설계는 분석서에 기술된 문제를 해결 방안으로 변경하는 작업이다. 분석과 설계. 완성된 설계는 요구를 만족시키는 특정한 솔루션이다. 문제를 정의하기 위해서 요구명세서를 작성하고 문제에 대한 솔루션을 만드는 설계 방안은 이에 제한받지 않는다. 즉 설계는 여러 가지 대안 중 여러 가지 제약 조건을 잘 만족시킬만한 하나의 솔루션을 선택하는 것이다. 이런 설계는 사용자와 개발자를 동시에 만족시켜야 하는 책임과 의무가 있다. 사용자는 설계를 보고 시스템이 어떤 기능을 하는지 이해하고 개발자는 시스템이 어떻게 동작하는지 이해하여야 한다.
일정 규모 이상의 소프트웨어 설계는 다음과 같은 분류를 해볼 수 있다.
소프트웨어 개발 방법의 근간인 개념과 원리로서 방법은 상황에 맞는 것을 적절하게 적용해야 한다. 구조적 설계는 구조적 분석의 ‘형식은 기능을 따른다’는 원리를 일관되게 적용한다. 자료흐름도에서 프로세스들의 패턴은 시스템의 구조를 결정한다. 여기서 분할과 정복이 사용되고 반복한다. 이러한 설계의 중심이 되는 원리들이 몇 가지가 있다.
✔️ 추상화(Abstraction)
추상화는 자세한 사항보다는 근본적 본질에 집중하는 것을 말한다. 다시 말해 자세한 사항은 처음부터 다루지 않고 전체적이고 포괄적인 개념부터 차례대로 세세하게 세분화함으로써 구체화해 나가는 것이다. 이러한 추상화는 어떤 결과가 얻어져야 하느냐는 명세화 관점을 어떻게 달성할 것인가라는 구현관점과는 별개로 하여 설계 작업에 좀 더 집중할 수 있게 만든다. 소프트웨어 설계에서의 대표적 추상화는 기능 추상화, 자료 추상화 및 제어 추상화가 있다.
✔️ 정보은닉(Information Hiding)
설계된 각 모듈은 자세한 처리 내용이 시스템의 다른 부분으로부터 감춰져야 한다는 것이 정보은닉이다. 즉 모듈 안의 내용을 보여주지 않고 잘 정의된 인터페이스를 통해 메시지를 전달하는 개념인데 설계상 모든 결정 사항들이 모듈 안에 감춰져서 다른 모듈이 접근하거나 변경하지 못하도록 하는 것이다. 이는 모듈화의 기준으로도 사용이 가능하며 이에 따른 모듈의 구현은 독립적이며 설계 과정에서 하나의 모듈이 변경되어도 다른 모듈 설계에는 영향을 주지 않는 장점이 있으며 모듈의 이해도도 높일 수 있다.
✔️ 단계적 분해
문제를 상위 개념부터 더 구체적인 단계로 하향식 분할하는 개념으로 보통 4가지 과정을 거친다. 먼저 문제를 기본 단위로 나누고 이를 독립된 문제로 구별한다. 이후 구분된 문제의 자세한 내용은 가능한 뒤로 미뤄두고 구체화 작업이 계속 점증적으로 일어난다는 것을 보여준다. 이러한 단계적 분해는 소프트웨어를 이루는 모듈에 대한 구체적 설계를 수행할 때 주로 사용된다. 단계적 분해는 문제를 해결할 만한 작은 문제로 나누고 구체화의 정도를 작게 하여 점증적으로 문제를 다루어 나가는 것이다. 구체적인 설계 결정을 뒤로 미룰 수 있고 조금씩 변경되므로 일관성을 유지 할 수 있는 장점이 있다.
✔️ 모듈화
소프트웨어 구조를 이루는 기본적인 블록이 모듈이다. 이 모듈이 구체적으로 어떤 단위를 말하는 것인가에 대해서는 일치되는 정의는 딱히 없다. 그러나 여러 개의 독립적인 프로그램으로 구성된 복합 시스템의 경우에는 시스템 설계 단계에서 모듈은 기능적으로 관련된 여러 개의 프로그램, 함수, 부프로그램의 묶음이기도 하고 추상화된 자료나 병행 처리 프로세스도 모듈로 불린다. 이 모듈의 공통 개념은 수행 가능 명령어, 자료 구조 또는 다른 모듈을 포함하고 있는 독립 단위이다. 모듈은 이름을 가지고 있으며 독립적으로 컴파일이 되고 다른 모듈을 사용할 수도 있으며 다른 프로그램에서 사용될 수도 있다.
모듈의 크기는 다양하다. 일반적으로 쉽게 이해될 수 있을 정도로 작아야 한다는 데 동의한다. 이런 모듈은 모듈을 이루는 각 요소가 공통의 목적을 달성하기 위해 얼마나 관련이 있는가를 나타내는 응집력을 갖는다. 즉, 응집력은 모듈 안의 요소들이 서로 관련된 정도를 말하며 모듈 설계의 목표는 강력한 응집력을 갖는 모듈을 만드는 것이다. 이러한 모듈 안에서 응집력의 정도를 마이어스는 일곱 가지 단계로 구분했는데 응집력이 강한 것부터 약한 것까지 ‘기능적 – 순차적 – 교환적 – 절차적 – 시간적 – 논리적 – 우연적’ 으로 나열할 수 있다.
모듈의 응집도와 같이 이야기되는 것이 결합도 이다. 결합도는 모듈 간 상호 의존하는 정도를 말하는데 모듈은 하나의 블랙박스로 다른 모듈과의 독립성이 높아야 한다. 이런 독립적 모듈이 되기 위해서는 다른 모듈과의 결합도가 약해야 하며 의존하는 모듈 또한 적어야 한다. 모듈 사이 의존하는 정도는 결합도가 강한 것부터 약한 것까지 ‘내용 – 공통 – 제어 – 스탬프 – 자료’ 결합으로 나열할 수 있다.
소프트웨어 개발에 있어 요구분석 단계는 중요하고도 어려운 단계이다. 많은 정보가 수집되어야 하며 의사소통의 어려움도 극복해야 하는 숙제 중 하나다. 이러한 작업을 증명하기 위한 방법은 매우 다양하다. 그중 프로세스 사이의 데이터 흐름을 파악하여 요구사항과 시스템을 파악하는 방법으로 구조적 분석이 있다. 구조적 분석(Structural Analysis)은 시스템의 기능적 요구사항을 구조화하고 조직화하는 단계이다. 이를 통해 요구사항 간의 관련성과 의존성을 이해하고 분해된 요구 모듈 간 관계를 분석하여 시스템의 구조를 설계할 수 있다. 구조적 분석은 데이터 흐름 다이어그램(DFD), 상태 전이 다이어그램, 클래스 다이어그램 등의 모델링 기법을 활용하여 수행된다. 전통적 방법으로서 이 단계에서는 요구사항이 어떻게 상호작용하고 구성되는지를 시각화하여 파악한다.
소프트웨어 개발 프로젝트 초기 단계부터 요구사항을 명확하게 이해하고 효과적으로 관리할 수 있는 구조적 분석은 궁극적으로 프로젝트의 성공 확률을 높일 수 있다. 이 방법은 크게 다음과 같은 단계로 수행되고 여기에 쓰이는 도구도 확인한다.
구조적 분석의 세부적인 과정은 크게 4단계로 나눠볼 수 있다.
시스템에 대한 요구사항을 수집하고 구조화 및 문서화한다. 이 요구사항은 고객, 사용자, 도메인 전문가 등과의 인터뷰, 설문조사, 관찰 등 다양한 방법으로 얻을 수 있으며 최종적으로 기능적 요구사항(시스템이 수행해야 하는 작업)과 비기능적 요구사항(성능, 안전성, 보안 등)으로 구분될 수 있다. 이 과정을 통해 모호한 내용을 명확히 하고 요구사항 간의 관련성과 의존성을 파악, 중복되거나 충돌하는 요구사항을 논리적 블록으로 그룹화, 해결한다.
명세화된 요구사항을 기반으로 소프트웨어의 핵심 문제를 파악하고 분석할 수 있는 구조로 정리한다. 이때 데이터 흐름도(DFD), 자료사전, 프로세스 명세 등의 도구를 활용한다.
– 주요 프로세스 식별: 수집된 요구사항을 기반으로 시스템 내의 주요 기능적 프로세스 식별하며 DFD로 표현 (시스템의 입력, 출력, 처리 과정 포함)
– 데이터 흐름 식별: 프로세스 간에 주고받는 데이터(정보) 흐름 식별
– 데이터 저장소 식별: 시스템 내 사용되는 데이터 저장소를 식별하는데 이는 데이터의 정의, 속성, 구조 등을 포함하고 이를 데이터 요소 간의 관계와 종속성을 파악
분석된 요구사항이 사용자의 기대와 일치하는지 검증하여 문제가 없는지 확인하는 단계로 사용자와 지속적인 의사소통으로 유스케이스 테스트나 프로토타입 방식을 사용하여 실제 사용자의 반응을 수집할 수 있다. 특히 요구사항과 부합하며 일관성 있는 모델을 구축하는 것이 중요한데 이 결과를 바탕으로 소프트웨어의 전체적인 아키텍처를 설계할 수 있다. 특히 모듈화, 계층화, 컴포넌트 구조 등을 고려하여 시스템 구조를 결정한다.
– 0레벨 DFD 작성: 주요 프로세스, 데이터 흐름, 데이터 저장소 등을 기반으로 0레벨 DFD를 작성하며 이 다이어그램은 시스템의 전체 구조를 보여주고 각 프로세스와 데이터 흐름의 관계를 가시화
– 상세화: 0레벨 DFD를 기반으로 더 상세한 1레벨 DFD를 작성하여 프로세스를 세분화하고 데이터 흐름을 더욱 구체적으로 표현
– 유효성 검사: 작성한 DFD를 검토, 논리적 오류나 모순점이 있는지 확인
작성한 DFD를 문서화하여 요구사항 명세서나 설계 문서에 포함한다. 이에 개발팀 간 의사소통을 원활하게 하고 시스템 구조를 이해하는 데 도움을 준다. 요구사항이 변경되거나 갱신이 되면 요구사항 관리를 통해 변경된 요구사항에 따른 작업의 우선순위를 조정하고 추적 관리한다. 이 문서들은 개발 및 테스트 과정에서 참고 자료로 활용한다. 다시 말해 문서화와 의사소통의 중요성은 각기 다음과 같다.