[EIP] 필기 1일차

필기 시험 날짜는 2024년 5월 9일(목)이다. 너무 늦게 공부를 시작했나 싶긴 하지만, 남은 이틀 동안 최대한 빠르게 많이 훑어보고 시험장에 들어가보자. 공부 재료는 역시나 시나공이다. 오늘 처음 회원가입하고 써보는데 무료로 많은 자료와 기출문제들이 있어서 이틀 동안 소화하기 알맞을 것 같다.

목표는 1일차에 쉬운 1,2,3과목에 대해 개념 훑어보기를 끝내고 확인차 기출을 풀어보는 것이다. 2일차 목표는 4과목을 기출 3회 정도 돌려보고, 5과목은 정리하면서 이해하기. 기출도 2회 정도는 풀 수 있으면 좋겠다.

1. 소프트웨어 설계

그럼, 1과목부터 시작! 소프트웨어 공학은 아쉽게도 학부 과정에서 듣지 못해서 조금 시간이 걸릴 수도 있을 것 같다. 소프트웨어 공학은 소프트웨어의 위험(risk)를 관리하기 위해 연구된 학문을 말한다. 품질 및 생상성 향상을 위해 여러가지 방법론과 도구, 관리 기법들이 만들어졌다. 가장 기본적인 원칙은 지속적인 검증과 기록이다.

1.1 개발 프로세스 모델

개발 방법론의 바탕이 되는 스프트웨어 생명 주기는 소프트웨어 정의, 운용, 유지보수 등의 과정을 단계별로 나뉜다. 각 단계들과 그 산출물로 생명 주기 모형(혹은 프로세스 모형)이 표현된다.

  • 폭포수(Waterfall) 모형 : 각 개발 단계를 철저히 검증 및 결과물을 산출한 후, 다음 단계를 진행하는 선형 순차적 모형이다.
    (타당성검토 -> 계획 -> 요구분석 -> 설계 -> 구현 -> 시험 -> 유지보수)
  • 나선형(Spiral) 모형 : 여러번 개발 과정을 거쳐 점진적으로 개발해 위험을 최소화한다.
    (계획수립 -> 위험분석 -> 개발/검증 -> 고객 평가) -> (계획수립 …)
  • 애자일(Agile) 모형 : 고객과의 소통에 초점을 맞춰 요구사항 변화에 유연하게 대응하도록 일정한 주기를 반복한다. (ex: Scrum, XP, Kanban, Lean, Crystal, ASD, FDD, DSDM, DAD 등)

Agile 개발은 프로세스나 도구보다는 협엽과 상호작용에 더 가치를 둔다. 문서보다 SW에, 계획보다 변화에 반응하는 것에 집중한다. 스크럼 개발 프로세스의 경우, 매일 회의에서 모든 요구사항이 담긴 백로그를 기반으로 단기 일정을 수립하고 테스팅을 수행한다. 이 과정을 스프린트(sprint)라고 부른다. XP(eXtrem Programming)의 경우 수시로 발생하는 요구사항에 대응하기 위해 짧고 반복적인 개발 주기로 개발 생산성을 높인다. small releases를 반복하면서 단순한 설계를 CI(지속적 통합)하여 design refactoring(improvement)한다. 이를 위해, 구현보다 테스트 케이스를 먼저 작성하고 자동화된 테스팅 도구를 사용하는 TDD를 사용한다.

1.2 요구사항 확인

요구사항은 소프트웨어가 제공하는 서비스(기능)에 대한 설명과 정상적으로 운영되기 위해 필요한 제약조건(비기능) 등을 말한다. 이러한 요구사항을 도출(elicitation), 분석(analysis), 명세(specification), 검증(validation)하는 활동을 요구사항 개발 프로세스라고 부른다. 산출물인 요구사항 명세는 수학적 표기법을 따르는 정형 명세 기법이나 자연어/다이어그램으로 이루어진 비정형 명세 기법으로 작성된다. 명세화를 위해서 요구사항을 분석할 때, UML, DFD(자료흐름도), DD(자료사전), ERD(개체관계도) 등의 도구를 사용한다. UML(Unified Modeling Language)는 개발자와 고객/사용자의 의사소통을 위해 표준화한 객체지향 모델링 언어이다. 객체지향 시스템 구조에 대해서 사물과 관계(연관/집합/포함/일반화/의존/실체화)를 E-R 다이어그램으로 작성할 수 있다. DFD(Data Flow Diagram)은 데이터의 흐름과 변환 과정, 기능을 다이어그램으로 기술하여 자료흐름 그래프 혹은 버블 차트라고도 불린다. DD(Data Dictionary)는 DFD의 자료를 설명한 메타데이터를 기록한다. 요구사항 분석을 자동화한 도구(CASE)로는 SADT, SREM, PSL/PSA, TAGS 등이 있다.

모듈을 통합하려면 먼저 현행시스템을 이해해야 한다. 시스템의 기능과 구성, 인터페이스를 파악하는 것이 먼저이고, 설계와 소프트웨어의 구성을 명시한 후, 하드웨어와 네트워크 구성을 작성해야한다. 이처럼 시스템을 여러 고유 모듈로 분할하여 그들 간의 인터페이스를 계층 구조로 문서화하는 기법을 HIPO(Hierarchy Input Process Output)이라고 부른다. 반드시 파악해야할 시스템에는 OS, DBMS, WAS 등이 있다. OS는 하드웨어와 사용자 간의 인터페이스로 응용 프로그램이 동작하는 환경을 제공한다. Windows, UNIX, Linux, MacOS, iOS, Android 등이 있다. DBMS는 데이터베이스와 사용자 간의 인터페이스로, 정보를 생성하고 관리한다. 모든 응용 프로그램이 데이터를 공유하도록, 파일 시스템의 종속성과 중복성 문제를 해결한 시스템이다. Oracle, MySQL, SQLite, MongoDB, Redis 등이 있다. WAS는 정적인 웹서버와 달리, 사용자의 요구에 동적으로 대응하기 위해 사용되는 미들웨어이다. 데이터 접근, 세션 관리, 트랜잭션 관리 등을 주로 데이터베이스와 연동하여 수행한다. Tomcat 등이 있다.

  • Diagram : 사물과 관계를 가시화된 도형으로 표현한다.
    • 구조적(structural) diagram : class, object, component, deployment, …
    • 기능적(behavioral) diagram
      • use-case : 사용자(actor) 관점의 기능
      • sequence : 시간순으로 상호 작용하는 과정 with 메시지
      • communication, state, activity, interaction overview, timing, …
  • UI : 사용자 인터페이스는 만족도에 가장 큰 영향을 끼쳐 변경이 가장 잦다. 크게 CommandLI, GraphicalUI, NaturalUI, VoiceUI, OrganicUI 등으로 나뉜다. UI 설계 도구에는 Wireframe, Mockup, Story-Board, Prototype, Use-case 등이 있다.

실사용자 입장에서 사용하기 쉽고, 오류파악이 쉽도록 검증해야 한다. 이를 위해 기능성, 신뢰성, 사용성, 효율성, 유지보수성, 이식성 등의 품질 요구사항이 존재한다.

1.3 애플리케이션 설계

UI와 diagram 등으로 요구사항에 대한 대비가 끝났다면, 이제 소프트웨어를 설계해야 한다. 시스템의 전체 구조를 계층적으로 설계한 후, 상세한 내부 구조 및 동작을 설계한다. 주어진 문제를 상위 개념부터(단계적 분해), 세분화하여(추상화), 기능과 정보를 모듈 단위로(모듈화) 나눠 독립되게(정보은닉) 설계해야 한다. 언뜻 보기에도 복잡하기에, 참고할 수 있는 전형적인 아키텍쳐 패턴들이 마련되어 있다. OSI 참조 모델 같은 Layers 패턴과 일대다 component를 연결하는 client-server 패턴 등이 가장 단순한 예시이다.

  • 파이프-필터 패턴 : 데이터 스트림의 각 단계를 filter로 캡슐화하여 pipe로 전송한다. 재사용성과 확장성이 좋아 다양한 파이프라인을 구축하기에 용이하다.

  • 모델-뷰-컨트롤러(MVC) 패턴 : 핵심 기능과 데이터를 보관하는 Model과 정보를 표시하는 View, 사용자의 입력을 처리하는 Controller로 서로 독립되어 구성된다. 여러 개의 뷰를 필요로 하는 대화형 애플리케이션에 용이하다.

변화하는 요구사항들에 대응하여 위와 같은 아키텍쳐 패턴에 따라 설계하기 위해 객체지향(Object-Oriented) 개념이 만들어졌다. 각 객체는 동작(함수)과 정보(데이터)를 캡슐화한 모듈이다. 객체(object, instance)를 정의하는 class는 곧 그 객체의 type이다. 객체들은 상속다형성을 통해 재사용성을 높일 수 있다. 시스템의 변경과 확장에 대처하기 위해 다음 다섯가지 기본 원칙(SOLID)이 제시되었다. 1) Single Responsibility : 객체는 하나의 책임만 가지도록 2) Open-Closed : 코드 변경 없이 기능을 추가할 수 있도록 3) Liskov Substitutioin : 부모가 가능한 것은 자식도 가능하도록 4) Interface Segregation : 필요 없는 인터페이스와는 독립되도록 5) Dependency Inversion : 추상성이 높은 클래스를 의존하도록

모듈화 정도를 판단하는 기준에는 결합도와 응집도, Fan-In/Out이 있다. 결합도(coupling)은 여러 모듈 간의 의존 정도를 나타낸다. 이상적인 순서대로 data-stamp-control-external-common-content 이다. stamp는 자료구조까지 일치해야하는 경우이다. 응집도(cohesion)은 한 모듈 내의 기능이 관련되어 있는 정도를 나타낸다. 이상적인 순서대로 functional-sequential-communication-procedural-temporal-logical-coincidental이다. 시스템의 복잡도를 판단하기 위해서 해당 모듈에서 호출하는 모듈의 수(Fan-In)와 해당 모듈이 호출되는 모듈의 수(Fan-out)이 쓰인다. In이 높을수록, Out이 낮을수록 좋다. 재사용성을 더 높이기 위해, 여러 프로그램에서도 사용할 수 있는 공통 모듈이 존재한다. 결합도가 낮고 응집도가 높아야 하며, 누구나 사용하기 쉽게 명세가 명확하게 작성되어야 한다.

  • code 부여 체계 : 데이터를 처리하는 과정에서 간소화 및 표준화하기 위해 특정 코드들을 부여하곤 한다. 순차코드, 블록코드(번호부), 10진코드(도서분류), 그룹분류코드(버전), 연상코드(약호), 표의숫자코드(수치), 합성코드 등이 있다.

전체 시스템 구조가 아키텍처 패턴으로 모델화되었다면, 독립적인 컴포넌트들과 그 관계(interface)를 설계하기 위해 디자인 패턴이 쓰인다. 검증된 범용적인 패턴 스타일들이기에 구조 파악이 용이하고, 유연한 대처가 가능해 생산성을 높인다. 그중 GoF 디자인 패턴은 크게 5개 생성 패턴과 7개 구조 패턴, 11개 행위 패턴으로 구성되어 있다.

설계와 구현이 완료되었다면 요구사항을 만족하였는지 검토할 필요가 있다. 검토(review) 방법에는 직접 설명하는 peer review, 사전 검토 후 짧은 회의로 지나가는 walk through, 제 3자가 명세와 비교하는 inspection 등이 있다.

2. 소프트웨어 개발

2.1 자료구조

자료구조는 array, stack, queue, linear list 등의 선형구조와 tree, graph 등의 비선형구조로 나뉜다. List는 배열처럼 연속적으로 저장되는 contiguous list와 각 노드에 값과 다음 노드의 주소 값을 저장하는 linked list로 나뉜다. 메모리 효율은 연속리스트가 좋지만, 노드의 삽입/삭제 등의 작업은 linked list가 유리하다. Stack은 LIFO(후입선출) 방식의 리스트 top 포인터 하나가 있다. 주로 interupt, function call, return address, 수식표기 등에 사용된다. Queue는 FIFO(선입선출) 방식의 리스트로 시작과 끝을 표시하는 두 개의 포인터가 있다. Deque는 양방향에서 입력과 출력이 가능하다. Graph는 무방향과 방향으로 나뉘는데, 최대 간선 수는 무방향그래프에서 n(n-1)/2이고, 방향그래프에서는 n(n-1)이다. Tree는 cycle을 이루지 않도록 구성한 graph이다. Root 노드(level 1)부터 leaf 노드까지의 노드 개수가 depth이다. 각 노드의 자식 노드 개수를 degree라고 부른다. 읽는 방식에 따라 preorder(A-B-C), inorder(B-A-C), postorder(B-C-A)로 나뉜다. 이 표기법은 수식에서도 유사하게 나타난다. prefix(+AB), infix(A+B), postfix(AB+)와 같다. stack에서 사용하기 위해 prefix나 postfix와 같이 표기한다.

  • Selection Sort
  • Bubble Sort
  • Quick Sort : conquer and divide 방식을 따른다. 평균적으로 O(nlogn), 최악의 경우 O(n^2)이다.
  • Heap Sort : 완전이진트리를 이용해 heap tree로 변환하여 정렬한다. O(nlogn)이다.
  • Merge Sort : O(n^2)이다.
  • binary search : 순서화된 파일을 둘로 나누어가며 key를 검색
  • Hashing 함수 : 제산법, 제곱법, 폴딩법, radix, 대수적코딩, 계수분석법, 무작위 등이 쓰인다.

2.2 개발 과정

개발 과정에서는 시스템 전체를 바라보기보다, 각 동작을 수행하는 모듈을 구현하고 테스트하는 것이 유용하다. 하나의 기능을 수행하며 독립적인 컴파일이 가능한 단위 모듈이 조합되어 더 큰 모듈과 시스템을 구성한다. 모듈 간의 통신을 인터페이스로 구현한 것이 IPC(Inter-Process Communication)이다. 공유공간, 소켓, 세마포, pipe, 메시지큐 등을 활용해 프로세스 간 통신까지 구현 가능하다. 단위 기능은 동적으로 단위 테스트를 거쳐 검증한다. 단위 모듈에 대한 테스트 케이스를 사용해 시스템 수준의 오류는 잡아낼 수 없다. 이때는 driver와 stuck으로 호출을 대신한다.

자연히 발생하는 소프트웨어의 변경 사항을 기록하는 SCM(형상 관리)를 통해 모든 개발 단계에서 생산성과 품질을 높일 수 있다. 변경 사항을 체계적으로 추적할 수 있고, 버전을 관리하며 협업하기 쉽다. Git, CVS, Subversion 등이 대표적인 형상 관리 도구이다. 변경된 소스 코드들을 통합하여 컴파일하고 실행, 배포하는 것을 CI(지속적 통합)이라고 부른다. Ant, Make, Maven, Gradle과 같은 빌드 자동화 도구가 유용하게 사용된다.

3. 데이터베이스 구축

3.1 DB 설계

데이터 베이스를 설계하는 순서는 다음 6단계를 따른다.
요구조건분석(명세서) => 개념적설계(E-R모델링) => 논리적설계(스키마 설계) => 물리적설계 => 구현(DB생성)
개념적 설계에서 요구 명세를 E-R 다이어그램으로 작성한다. E-R 다이어 그램은 객체(Entity)와 그들의 관계(Relation)을 단순화, 추상화한다. 논리적 설계 단계에서는 작성된 데이터 타입들과 그 관계를 논리적인 자료구조로 변환(mapping)하여 스키마와 트랜잭션 인터페이스를 설계한다. 물리적 설계는 논리적 구조로 표현된 데이터를 물리적으로 저장하는 방법을 결정한다.

가장 널리 사용되는 데이터 모델은 2차원적인 Table을 이용한 관계형(Relational) 모델이다. 하나의 테이블이 relation을 가리키며, 객체의 특성을 attribute, 그 속성들의 집합을 tupe, 각 속성이 가질 수 있는 값의 집합을 domain이라고 부른다. 한 테이블에 존재하는 tuple과 attribute들 사이에는 중복이 없으며, 순서 또한 없다. 각 tuple을 식별하기 위한 속성들의 부분집합을 Key라고 칭한다. 식별가능하게 하는 속성이 부분집합들은 모두 후보키(candidate key)이며, 그중 null을 가지지 않은 하나의 키만을 기본키(primary key), 그 외에는 대체키(alternate key)라고 부른다. 이들은 유일성 뿐만 아니라 최소성 역시 만족시킨다. 유일성만을 만족시키는 super key와 외부 테이블의 기본키를 참조하는 foregin key도 존재한다.

모든 테이블은 단순하고 중복이 없는 것이 유리하다. 큰 관계형 스키마를 더 작은 속성을 가진 스키마로 분해하여 하나의 종속성이 하나의 테이블에 표현될 수 있도록 하는 과정을 정규화라고 부른다. 만족시켜야할 제약 조건에 따라 6개의 정규화 방법이 존재한다. 차례로 도메인이 원자값(atomic)하도록 하는 1정규화, 부분적 함수 종속을 제거하는 2정규화, 이행적 함수 종속을 제거하는 3정규화, 결정자이면서 후보키가 아닌 것을 제거하는 BCNF, 다치종속을 제거하는 4NF, 조인 종속성을 이용하는 5정규화이다. 만약 잘 정규화 되지 않으면 데이터들이 중복되어 원치않은 삽입/삭제/갱신 이상(Anomaly) 현상이 발생할 수 있다. 그러나 때로는 성능과 편의성을 위해 의도적으로 데이터를 통합/중복/분리하는 반정규화를 수행하기도 한다. 이와 같은 논리적 설계를 거쳐 만들어진 시스템의 각 테이블들의 메타 데이터는 시스템 카탈로그에 저장된다. 해당 데이터 사전(DD)는 DBMS가 직접 생성/유지한다.

3.2 DB 관리

데이터베이스의 상태를 바꾸는 작업은 일련의 연산들로 처리된다. 사용자가 동시에 여러 작업을 요구할 경우 발생하는 오류를 막기 위해 정한 하나의 상태 변화에 대응하는 논리적 작업 단위를 트랜잭션이라고 부른다. 트랜잭션의 연산은 완전히 완료되기 전까지 다른 연산이 끼어들거나 중간 수행 결과를 참조할 수 없다. 만약 작업 중 중단된다면 모든 변경사항은 복구(Rollback)되어야 한다.

Index는 데이터가 저장된 물리적 위치에 빠르게 접근하기 위해 구성된 key-pointer 쌍이다. View는 권한에 따라 접근 가능한 자료만을 보여주는 가상 테이블이다. 논리적으로 독립되어 보안 및 관리가 용이하다. 수정하려면 view를 생성할 때 원본 테이블의 기본키를 포함해야한다.

CRUD는 생성, 읽기, 갱신, 삭제를 통해 테이블에 변화를 주는 작업이다. 스키마, 도메인, 테이블, 뷰, 인덱스에 대한 CRUD 작업은 DDL*(Data Define Language)을 사용한다. CREATE(정의), ALTER(변경), DROP(삭제) 명령어들을 통해 논리적 구조와 물리적 구조를 사상한다. 실질적으로 저장된 데이터를 처리하는 DML(Data Manipulation Language)에는 SELECT(검색), INSERT(삽입), DELETE(삭제), UPDATE(변경) 명령어들이 있다. 데이터 관리를 위한 DCL(Data Control Language)에는 COMMIT(저장), ROLLBACK(복구), GRANT(권한부여), REVOKD(권한삭제) 명령어들이 있다.

ALTER TABLE student ADD std_nu VARCHAR(10) NOT NULL;
DROP TABLE student [CASCADE | RESTRICT];

INSERT INTO student(name, std_num) VALUES ('alice', 001);
UPDATE student SET std_num=003 WHERE name='alice';
DELETE FROM student WHERE name='alice';
SELECT DISTINCT std_num FROM student;

GRANT [RESOURCE | CONNECT] TO user1;
REVOKE [GRANT OPTION FOR] UPDATE ON student FROM user1 [CASCADE | RESTRICT];

GROUP BY 절에 지정된 그룹별로 속성의 값을 집계하는 COUNT, SUM, AVG, MAX, MIN, STDDEV(표준변차), VARIANCE(분산) 등의 명령어들도 있다. 여러 테이블의 데이터를 통합하는 집합 연산자에는 UNION(중복x), UNION ALL, INTERSECT(교집합), EXCEPT가 있다. JOIN절 역시 알아둬야 한다.

3.3 DB 접근

DBMS는 대용량 데이터를 처리하기 위한 목적일 경우가 많기 때문에, 논리적으로는 하나의 시스템이지만 물리적으로는 네트워크를 통해 연결된 분산데이터베이스가 많이 사용된다. 서버와 저장장치가 직접 케이블로 연결된 방식은 DAS, 네트워크를 통해 연결된 방식을 NAS라고 부른다.

DBMS에 접근하기 위한 API에는 JDBC, ODBC, MyBatis 등이 있다. 한편, 객체지향 프로그래밍에서 Object와 Relational db를 Mapping하는 기술을 ORM이라고 부른다.

4. 프로그래밍 언어 활용

대부분의 내용이 언어별 문법과 데이터 구조의 표준이다. C, JAVA, PYTHON으로 직접 코드를 짜본 사람이라면 전혀 신경쓰지 않아도 무방하다. 필요하다면 code/0-Education에서 예제를 잠시 훑어보고 가도 좋다.

컴퓨터 구조와 운영체제에 대한 기초적인 내용이 다소 포함되어 있다. 그러나 페이징/세그멘테이션과 프로세스 스케줄링에 대해 알고 있다면 파일 구조에 대한 내용을 넘어가도 좋다.

OSI 모델과 IP 주소에 기반한 정말 기초적인 네트워크 지식을 묻는다. 계층별 프로토콜을 알고 있다면 마찬가지로 넘어가자.