Search

a0_4.1_1. [entry] title: 생성형 AI와 포멀하고 딱딱한 소프트웨어공학의 조합

생성
🚀 prev note
🚀 next note
💡 아이디어조각
13 more properties
관심가지게 된 계기
1.
메모를 어떻게 재사용할까 고민하다가 관심을 가지게 되었다. 아래 글에서는 메모라는 행위와 소프트웨어공학이 어떤 연관이 있는지에 대해 이야기한다.
2.
요청한 코드에 대한 결과물에 항상 아쉬움이 느껴지는데, 그 이유는 정확하게 요청하지 못했기 때문이라는 생각이 들었다. 정보처리기사 시험을 공부하다 보면 도대체 이렇게까지 용어를 정의해야 하는 이유가 무엇일까 싶은 생각이 들기까지 한다. 이들은 결국 AI 대신 사람을 사용했던 것이다. 사람이나 AI 나, 명확한 입력을 주어야 명확한 출력이 나온다.
3.
프로토타이핑 중 개발을 해야만 하는 상황까지 갔거나, 실제로 개발 프로젝트에 착수해야 하는 상황이 있을지도 모른다. 이런 상황에는 어떤 순서로 생각을 해나갈 것인가.
딱딱하고 포멀한 소프트웨어공학, 내가 생각하기는 번거로운데 누가 옆에서 조언을 해준다면 어떨까.
AI가 코드를 잘 짜도 이것을 개선하려면 AI에게 코드를 잘 짜라고 오더해야 한다. AI가 코드를 잘 짜도 구조적으로 어떻게 만들어진 것인지 이해할 수 있어야 한다.디자인패턴은 문제를 해결하는 일에 도움이 될만한 코드 구조화 패턴이라고 생각하면 된다.
각각의 패턴들은 자신에게 맞는 상황에서 가장 잘 구조화될 수 있다.
예를 들어, 외부에서 접근 가능한 인터페이스를 하나의 클래스에 모아 두는 퍼사드 패턴이나, 복잡한 시스템에서 서버 역할을 하며 교통을 정리하는 중재자 패턴을 잘못 사용하면 해당 클래스가 모든 것을 가진 한 놈이 되어버릴 수도 있다.
프롬프트 예시
GPT야, 이러이러한 요구사항에 맞추어 저러저러한 것을 개발하려고 해. 그 중, 이 부분을 먼저 구현해 보려고 해. 어떤 디자인 패턴을 사용하면 좋을지 장단점을 분석하고 제안해 줘. (구현이 필요한 경우) … 제안 내용대로 간단히 프로그래밍해줘. … 소스코드의 구현부를 생략하지 마.
GPT야, 너가 만든 코드를 바탕으로 mermaid UML 다이어그램을 만들어서 보고서로 정리해줘.
아키텍처 패턴은 디자인패턴보다 추상화된 개념으로, 시스템을 구성하는 패턴이다. 레이어 패턴, 클라이언트-서버 패턴, 마스터-슬레이브 패턴 등. 디자인 패턴이 만들어내는 서비스, 모듈의 구조, IPC, EAI, ESB 그 무엇과도 연관이 있는 추상적인 개념이라고 볼 수 있다.
시스템 통합의 방법과 그 규모에 따라 구분해 보면 IPC, EAI, ESB 같은 키워드를 찾을 수 있다.
IPC
EAI
ESB
약어
Inter-Process Communication
Enterprise Architecture Integration
Enterprise Service Bus
통합의 범위
프로세스 간 통합
회사 애플리케이션 간 통합
회사 전체 시스템 통합
그래픽 분석기법을 이용한 소프트웨어 모델링: 프로그래머들은 소프트웨어 모델의 일부분을 다이어그램과 문서로 나타낸다. 다만 한 줄의 글보다 한 폭의 그림이 이해하기 쉽다. 선대 프로그래머들은 어떻게 하면 더 직관적으로 시스템을 표현하는 그림을 그릴 수 있을까를 고민했다.
UML과 객체지향 분석기법
럼바우의 그래픽 분석기법
럼바우는 UML의 창시자다.
럼바우는 객체 (시스템에 요구되는 객체의 속성과 연산을 표시하는 것), 동적 (상태도를 이용해 시간에 따른 흐름제어를 나타내는 것), 기능 (functional. 함수니까 IO가 표현되어야 한다. DFD로 IO를 표현하는 것) 모델링 순서대로 하면 시스템을 표현하기 편한 것 같다는 것을 생각해냈다.
다양한 상태 다이어그램들. 이벤트를 명시하는 것이 중요하다.
DFD
기능 모델링에 사용되는 DFD는 자료 사전이라는 문서가 추가로 필요하다.
제이콥슨의 그래픽 분석기법
제이콥슨도 UML의 창시자다.
제이콥슨은 사용자의 관점에서 상호작용을 모델링했다.
이런 방법들이 조합되고 발전하여 UML이라는 그래픽 표현 표준을 만들었다. UML은 개발자간 소통이나 프로젝트 이해관계자 사이의 소통의 표준이라는 점에서 훌륭하다. 소프트웨어 개발 방법론에 호환되지만, UML이 소프트웨어 설계 시 생각의 흐름을 대변한다고 하기는 어렵다. 따라서 UML을 신봉하기보다, 어떤 UML을 그리는 것이 어떤 도움이 될지 고민하고, UML을 그려내기까지의 사고를 어떻게 선형적으로 밟아 나갈 수 있는지에 대한 고민이 필요하다.
일례로, UML에는 해당되지 않지만 Coad와 Yourdon의 ERD는 현실을 그래픽으로 표현하는 일을 고민했다. 그만큼 가장 앞단계에서 시도해보기 쉽고 간편하다. 한편 이것은 데이터베이스를 설계할 때에도 이용되는데, 마찬가지로 데이터베이스 설계의 첫 단계인 개념적 설계 단계에서 이용된다.
데이터베이스 스키마
분류
설계
단계
개념 → 외부 → 내부
개념적 → 논리적 → 물리적
나누는 기준
추상화 수준에 따라
설계 순서에 따라
이렇게 집대성되어가고 있는 UML은 두 가지로 나누어볼 수 있다. 정적(구조) 다이어그램, 동적(행위) 다이어그램이 바로 그것이다.
정적(구조) 다이어그램 예시
동적(행위) 다이어그램 예시
시퀀스 다이어그램
소프트웨어 품질에 대한 국제표준 (ISO 25000)
테스트
정적 테스트와 코드 리뷰
워크쓰루: 간단한 테스트 케이스들을 넣어보면서 수작업으로 소스코드를 한발한발 가보는 것. 한편, 인스펙션 전 단계에서 명세서 까놓고 하는 회의 정도로 여기는 경우도 있다. 워크쓰루가 의미하는 바는 조금씩 다르다. 그게 중요할까.
인스펙션: 소스코드 저자 외 다른 전문가가 검사하는 가장 공식적인 리뷰 기법이다. 문서화된 절차를 기반으로 소프트웨어 명세를 만족하는지 검증한다.
프롬프트 예시
GPT야, 이 내용을 너가 작성한다면 어떻게 짤 것 같아? (10분마다 한번씩 조언해줘)
GPT야, 이것은 소스코드 가이드라인이야. 코드 인스펙션을 실행해줘.
동적 테스트에는 두 가지 관점이 있다. 프로그램이 어떻게 작성되었는지 충분히 의식하고 만든 테스팅인가 아닌가. 전자를 화이트박스 테스팅, 후자를 블랙박스 테스팅이라고 한다. 둘 다 엄연히 테스트 케이스를 작성하고 실행하는 테스팅이다.
화이트박스 테스팅
기초 경로 테스팅 (Base Path Testing): LLM의 발전으로 이것도 계산을 맡길 수 있다. McCabe가 제안한 순환복잡도(cyclomatic)를 측정한다. 그래프에 의해 닫힌 영역 + 외부영역(1) 을 더해 계산할 수 있다.
제어구조 테스팅 (Control Structure Testing): 테스트 케이스 설계 기법이다. 흔히 유닛테스트 등을 작성하는 것과 블랙박스 테스팅을 동치로 여기기도 한다. 하지만 사실은 변수의 변화, 루프, 조건 등을 골고루 훑어줄 수 있는 테스트케이스를 작성하라는 지침은 화이트박스 테스팅에 있다. 모듈을 작성하면서 이런 제어구조를 고려하여 유닛테스트를 동시에 작성하는 사람들도 있는데, 화이트박스 테스트가 개발 초기 사용된다고 이야기하는 이유가 바로 이것이다. 함수 커버리지는 어떤 함수가 최소 1번 이상 호출되었는지를 기준으로 커버리지를 계산한다. 구문(Statement) 커버리지는 라인(Line) 커버리지라고도 불린다. 프로덕션 코드의 전체 구문 중 몇 줄의 구문이 실행되었는지를 기준으로 판단한다. 결정(Decision) 커버리지는 브랜치(Branch) 커버리지라고도 불린다. 프로덕션 코드에 조건문이 있는 경우, 조건문의 전체 조건식이 True인 케이스, False인 케이스 2가지가 최소한 한번 실행되면 충족된다. 개별 조건식의 개수와는 상관없이 최소 2개의 테스트 케이스로 충족이 가능하다. 조건 커버리지는 결정 커버리지와 다르게, 전체 조건식이 아니라 개별 조건식을 기준으로 판단한다.
블랙박스 테스팅
원인결과그래프 테스팅 (Cause-Effect Graphing Testing): 어떤 플래그의 조합이 입력하는지에 따라 결과가 미묘하게 바뀌는 CLI 프로그램을 만든다고 생각해보자. 이 의존관계들을 어떻게 표현할 것인가? 이런 상황에서 알아보면 좋은 방법을 담고 있다.
경계값 분석, 동등 분할 테스팅 (Equivalance Partitioning Testing): 우리에게 가장 익숙한 테스트다. 입력가능범위가 실수값이어서 무한한 경우, 이들 모두를 넣어보는 것이 아니라 논리적으로 의미있을 것 같은 구간을 정하고 구간을 대표하는 값들과 경계값들만 넣어보는 것이다.
비교 테스팅 (Comparision Test): 큰 리팩터링이 있을 때 유용할 수 있다. 잘 작동하던 과거 버전을 정답으로 삼고 테스트하는 방법이다.
뭐 황당하게는 오류 예측 검사 이런것도 있긴 한데 실용적이지 않아서 패스.
응집도(Cohesion): 모듈 단위에서 코드를 어떤 기준으로 뭉치고 분해할 것인가를 고민할 때 도움이 될 지침이다. 각각은 레벨이 있어서, 높은 레벨을 달성할수록 더욱 좋은 응집성이라고 본다.
기능적(Functional): 모듈을 구성하는 기능들이 하나의 문제를 풀기 위해 모여있는 경우.
순차적(Sequential): 모듈을 구성하는 기능들이 연쇄적으로 입출력값을 주고받는 경우.
통신적(Communication): 입출력이 같지만 기능이 다른 경우.
절차적(Procedural): 모듈을 구성하는 기능들을 순서대로 실행해야 하는 경우.
시간적(Temporal): 기능들이 비슷한 시간 내에 실행되는 경우.
논리적(Logical): 비슷한 성격의 애들만 모여 있는 경우.
결합도(Coupling): 모듈간 상호의존 정도를 의미한다. 응집도와 마찬가지로 코드를 어떤 기준으로 뭉치고 분해할 것인가를 고민할 때 도움이 될 지침이다. 결합도에도 레벨이 있어서, 낮은 결합도를 달성할수록 더욱 좋은 프로그램이다.
내용(Content): 다른 모듈의 내부 기능이나 내부 자료를 직접 참조하는 경우. 클래스 멤버 변수를 외부에서 일정한 규칙 없이 막 변경하는 경우도 이에 속한다. 클래스 다이어그램에서 보통 실선으로 그리는 경우.
공통(Common): 공유되는 공통 데이터 영역을 사용하는 경우. 프로젝트 전체에서 사용하는 config 파일이나 객체를 두는 경우도 이 경우에 속한다고 할 수 있다. 머신러닝 하는 사람들에게는 유용하고 일반적인 패턴이지만, 일반적인 서비스에서 결합도를 낮추고 뜯어내야 하는 입장에서는 개빡칠만하긴 하다. 이것도 인정하긴 해야한다. 외부(External)와 차이라면 모듈 밖에 있다는 것.
외부(External): 외부에 노출하려고 전역에 만들어 둔 상수값 등을 다른 모듈에서 사용하는 경우. 여기서부터는 슬슬 안티패턴이다. 공통(Common)과 차이라면 모듈 안에 있다는 것.
제어(Control): 흐름을 제어하는 요소들(함수 포인터, 태그, 플래그 등)을 서로 전달하는 경우. 이것은 세련되게 사용한다면 정말 훌륭하다.
스탬프(Stamp): 배열, 튜플, 특정 자료구조 등이 모듈 간 인터페이스인 경우. 가령, int[] 를 파라미터로 받는 함수를 호출. 직접 정의한 클래스 인스턴스 타입을 전달하는 경우.
데이터(Data): 데이터의 기본 요소가 인터페이스인 경우. 가령, int 를 파라미터로 받는 함수를 호출.
프로젝트 일정 관리: 대부분의 경우 SI에서나 쓸법한 방법을 사용한다. 그런 것에는 아직 큰 관심이 없다. 얼마나 유용한지도 모르겠고, 이제 라인 수와 생산성을 동치로 여기는 것이 무의미한 시대가 아닐까.
정성적으로는 Function Point (소프트웨어 기능 증대에 가중치를 부여) 또는 Putnam (생명 주기의 노력 분포를 가정) 모형을 사용하기도 한다.
그나마 볼만한 것은 PERT가 작업 간 의존관계를 표현하는 방식인데, 이건 이제 생산성 도구를 이용해 칸반이랑 쉽게 통합될 수 있고, 여기에서 제안하는 생산성 지표도 코드 라인 수로 표현된다는 점에서 ((4기대+낙관+비관)/6) 별로 매력적이지 않다.
실제 개발 순서를 기준으로 도구들을 다시 정리한다면 다음과 같다. 애자일한 개발 과정 속에서도 생략하기 어렵다고 생각되는 것은 ‘필수’ 를 앞에 붙였다.
요구사항 정리, 기능 정리, 일정 추산
필수: 요구사항이 모호하지만 개발 과정의 상호작용이 보장되어 프로토타입 개발 모형으로 개발할 수 있는 상황인지, 자율주행 기술 개발처럼 법적-기술적 한계가 있기 때문에 나선형 모델로 개발해야 하는 상황인지 판단한다. 워터폴 프로젝트는 고려하지 않는다.
필수: UML 동적(행위) 다이어그램 중 유즈케이스 다이어그램 - 단순히 그린다가 중요한 것이 아니다. 작은 요구사항이 있을 때 이를 충족시키기 위해 법적으로, UX적으로, 개발을 최소화하고 사용자에게는 시스템이 동작한다는 착각을 줄 방법을 고민한다. 다이어그램의 뼈대를 잡기 위해 Diagrams: Show Me (GPTs) 과 같은 도구를 이용한다. Mermaid는 다이어그램을 코드로 그릴 수 있게 도와준다.
graph TD
  hello --> world
Mermaid
복사
선택: PERT 차트
선택: 요구사항 명세의 적절성 검토를 위한 전문가 피드백 (LLM 사용가능)
사용자 인터페이스 설계
선택: 전문가 피드백 (LLM 사용가능)
아키텍처 설계, 인터페이스 정의
필수: ERD
필수: 시스템 단위 아키텍처 패턴 선정
선택: UML 정적(구조) 다이어그램 중 클래스 다이어그램
선택: UML 동적(구조) 다이어그램 중 시퀀스 다이어그램
선택: 전문가 피드백 (LLM 사용가능)
모듈, 컴포넌트, 알고리즘, 데이터베이스 설계
선택: UML 정적(구조) 다이어그램 중 모듈 다이어그램
선택: 순차 다이어그램 (상호작용하는 시스템이나 객체들이 주고받는 메시지를 표현) 과 상태 다이어그램 (하나의 객체가 상태 변화나 상호작용에 따라 상태가 어떻게 변화하는지를 표현)
선택: 응집도와 결합도 고려
선택: 전문가 피드백 (LLM 사용가능)
개발환경 세팅
필수: git, lint, formater 등 기본적인 개발시스템 템플릿 로드
선택: CI/CD, LLM 시스템 템플릿 로드
소스코드 작성
선택: 다이어그램 소스코드 변환 (LLM 사용가능)
선택: 페어 프로그래밍과 워크쓰루 (LLM 사용가능)
선택: 응집도와 결합도 인스펙션 (LLM 사용가능)
선택: 화이트박스 테스트 작성 (LLM 사용가능)
선택: 블랙박스 테스트 작성 (LLM 사용가능)