-
AI가 코드를 잘 짜도 이것을 개선하려면 AI에게 코드를 잘 짜라고 오더해야 한다. AI가 코드를 잘 짜도 구조적으로 어떻게 만들어진 것인지 이해할 수 있어야 한다.디자인패턴은 문제를 해결하는 일에 도움이 될만한 코드 구조화 패턴이라고 생각하면 된다.
- 이때 GoF(Gang of Four)이라고 부르는 이유는 사람 네 명(Four)이 정립해서다.
-
각각의 패턴들은 자신에게 맞는 상황에서 가장 잘 구조화될 수 있다.
- 예를 들어, 외부에서 접근 가능한 인터페이스를 하나의 클래스에 모아 두는 퍼사드 패턴이나, 복잡한 시스템에서 서버 역할을 하며 교통을 정리하는 중재자 패턴을 잘못 사용하면 해당 클래스가 모든 것을 가진 한 놈이 되어버릴 수도 있다.
-
프롬프트 예시
- 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은 두 가지로 나누어볼 수 있다. 정적(구조) 다이어그램, 동적(행위) 다이어그램이 바로 그것이다.
-
정적(구조) 다이어그램 예시
-
동적(행위) 다이어그램 예시
시퀀스 다이어그램
-
소프트웨어 품질에 대한 국제표준 (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) 별로 매력적이지 않다.