공부하는 블로그

#5 - 요구사항 분석 본문

design patterns

#5 - 요구사항 분석

devtimothy 2019. 3. 23. 14:17

Head first OOAD를 타입스크립트로 읽기 #5 - 요구사항 분석

본 블로깅은 Head first OOAD: 세상을 설계하는 객체지향 방법론 (한빛미디어) 책을 Typescript 문법으로 전환하며 공부하는 글입니다.

글을 읽기 전에, 광고 배너 한번씩만 클릭 부탁드립니다. 블로그 운영에 큰 보탬이 됩니다 :)

새로운 프로그램을 만든다

고갱님: 강아지가 밖에 나가고 싶을때마다 밤낮으로 우리를 귀찮게 한다. 그래서 강아지가 드나들 수 있는 문을 버튼 하나로 동작할 수 있게 하고 싶다.

기본 코드 작성

// DogDoor.ts
class DogDoor {
 private _open: boolean;

 public DogDoor() {
   this._open = false;
}

 public open(): void {
   console.log("The dog door opens.");
   this._open = true;
}

 public close(): void {
   console.log("The dog door closes.");
   this._open = false;
}

 public isOpen(): boolean {
   return this._open;
}
}

// Remote.ts
import { DogDoor } from "./DogDoor";
export type Remote = InstanceType<typeof Remote>;
export const Remote = class {
 private _door: DogDoor;
 constructor(door: DogDoor) {
   this._door = door;
}
 public pressButton(): void {
   console.log(`Pressing the remote control button...`);
   if (this._door.isOpen()) {
     this._door.close();
  } else {
     this._door.open();
  }
}
};

// DogDoorSimulator.ts
import { DogDoor } from "./src/DogDoor";
import { Remote } from "./src/Remote";
export class DogDoorSimulator {
 public main(): void {
   const door: DogDoor = new DogDoor();
   const remote: Remote = new Remote(door);

   console.log("Fido barks to go outside...");
   remote.pressButton();

   console.log("\nFido has gone outside...");
   remote.pressButton();

   console.log("\nFido's all done...");
   remote.pressButton();

   console.log("\nFido's back inside...");
   remote.pressButton();
}
}

new DogDoorSimulator().main();

기본 코드를 작성 후 고갱님께 보여주니 빨리 안 닫히네요…, 토끼가 부엌으로 들어오면 어쩌죠? 와 같은 말을 한다면 어떨까? 동작하는 코드를 보여주니 고갱님이 여러 돌발상황을 이야기해주기 시작했다. 토끼 얘기를 한 이유는 깜빡하고 문 닫는 일을 잊은 경우 이다. 그렇다고 고갱님께 탓을 할 수는 없다. 이럴 때는 요구사항을 더 수집한다.

요구사항이란...

특정 상품이나 서비스가 어떤 것이어야 하는지 또는 무엇을 수행해야 하는지를 자세하게 설명하는 것.

우리 시스템(강아지 문)이 올바르게(고객이 만족하는) 동작하기 위해 수행하는(열기, 닫기, 쥐가 못 들어오게 하기…) 특정한 (한가지 일을 해결 후에 테스트함) 하나의 일.

요구사항 추가

  • 문이 2-3초 후에 자동으로 닫히도록 해주라.

  • 리모콘 버튼은 하나면 좋겠다. 열렸을때 누르면 닫히고, 닫혔을 때 누르면 열리게.

  • 개의 키는 30센치 정도인데, 몸이 걸리지 않게 해달라.

가끔은 고객에 따라서 자신이 뭘 원하는지 잘 모른다. 요구사항 물어보고 그냥 저걸로 끝났다! 하지말고 고객이 정말 원하는 것을 알아내도록 한다. 그래서 고객이 요구하는 것 이상을 추측해서 미리 요구사항에 대비할 수 있다. 개발자 입장에서야 고객 탓을 하겠지만, 사실 요구사항을 이끌어내지 못한 우리의 문제도 있다.

강아지 문이 하는 일

  1. 개가 밖에 나가고 싶어 짖는다.

  2. 주인이 개가 짖는 것을 듣는다.

  3. 주인이 리모콘 버튼을 누른다.

  4. 강아지 문이 열린다.

  5. 개가 밖에 나간다.

  6. 개가 밖에서 쉬한다.

  7. 개가 안으로 들어온다.

  8. 강아지 문이 자동으로 닫힌다.

사실 요구사항을 온전히 수집하는 일은 까다롭고 어려운 일이다. 생각할 것도 많고, 예기치 못한 상황을 예상해야한다. 단순히 강아지 문만 만든다고 해결되는 것은 아니다. 우리에게는 리모콘도 만들어야 하는 책임이 있다. 우리는 별개라고 생각할 수 있지만 고객은 그렇지 않다.

회사 프로젝트로 생각해보면, 고객은 심지어 알림톡 글씨체가 왜 이렇게 나가냐며 우리에게 문의하기도 한다. 본인 카톡을 보여주면서 말이다. 😭 우리 앱을 사용하다가 문제가 발생하면, 우리 잘못이 아니더라도 우리에게 문의한다.

이제 예외 사항을 생각해보자.

예외 사항

  1. 개가 밖에 나가고 싶어 짖는다. - 개가 짖기만 하나?

  2. 주인이 개가 짖는 것을 듣는다. - 주인이 집에 없다면?, 짖는 소리를 못 듣는다면?

  3. 주인이 리모콘 버튼을 누른다. - 나가고 싶어서 짖는 게 아니라면?, 주인이 오해해서 문을 열면?

  4. 강아지 문이 열린다. - 뭔가가 걸리는 경우? 하드웨어 문제니 신경 안써도 될까?

  5. 개가 밖에 나간다. - 안 나가고 집에 버티고 있다면?

  6. 개가 밖에서 쉬한다.

  7. 개가 안으로 들어온다. - 볼일을 다 안 봤는데 문이 닫히면?

  8. 강아지 문이 자동으로 닫힌다. - 밖에서 못 들어왔다면?

위와 같은 예외 사항을 생각해보았다. 이제 대체 경로를 추가해본다.

6.1 문이 자동으로 닫힌다.

6.2 개가 들어오려고 짖는다.

6.3 주인이 개가 짖는 것을 듣는다.

6.4 주인이 리모콘 버튼을 누른다.

6.5 강아지 문이 열린다.

위와 같은 플로우 작성하는 것을 유스케이스라고 한다. 시스템이 어떤 일을 수행하기 위해 거쳐야 하는 단계를 말한다.

유스케이스

고객의 특정한 목표를 달성하기 위해 내 시스템이 사용자 혹은 다른 시스템과 어떻게 상호작용하는지를 전달하는 하나 이상의 시나리오

  • 사용자는 시스템의 부분이 아니고, 시스템의 외부에 있다.

  • 하나의 유스케이스는 하나의 목표에만 집중한다. 개 주인을 위한 하나의 목표는 자신이 침대에서 나오지 않고 개가 밖으로 나갈 수 있게 하는 것이다.

  • 만일 개 주인이 개가 얼마나 자주 문을 드나드는지 기록하고 싶다는 문제는 또 다른 문제이다. 그에 맞게 또 다른 유스케이스가 필요할 것이다.

  • 고객의 목표가 유스케이스의 핵심이다. 우리는 고객 중심, 고객이 고객의 목표를 달성하도록 돕는다.

  • 유스케이스는 "무엇"에 관한 것이다. "어떻게"는 나중이다. 그리고 "무엇"을 "해야" 하는지를 정한다.

세가지 부분
  1. 명확한 가치: 전체 유스케이스

  2. 시작과 종료: 1번과 8번

  3. 외부 기동자(액터): 개

살펴보면 유스케이스는 Remote 클래스나 DogDoor 클래스에 대해 구체적으로 묘사하지 않는다. 유스케이스는 어디까지나 고객을 위해 필요하다. 너무 프로그래밍 용어 사용해가면서 자세히 기술하면 오히려 유용하지 않다.

요구사항 체크

고객의 요구 사항을 가지고 유스케이스에 적용해보자.

1. 강아지 문은 열려 있을 때 35cm 이상이어야 한다.
2. 리모콘에 있는 하나의 버튼은 문이 닫혔을 때 누르면 열리고, 열렸을 때 누르면 닫힌다.
3. 강아지문이 한번 열린 후, 닫지 않으면 자동으로 닫혀야 한다.
  1. 개가 밖에 나가고 싶어 짖는다. - N/A

  2. 주인이 개가 짖는 것을 듣는다. - N/A

  3. 주인이 리모콘 버튼을 누른다. - 2

  4. 강아지 문이 열린다. - 2

  5. 개가 밖에 나간다. - 1

  6. 개가 밖에서 쉬한다. - N/A

    1. 문이 자동으로 닫힌다. - 3

    2. 개가 들어오려고 짖는다. - N/A

    3. 주인이 개가 짖는 것을 듣는다. - N/A

    4. 주인이 리모콘 버튼을 누른다. - 2

    5. 강아지 문이 열린다. - 2

  7. 개가 안으로 들어온다. - 1

  8. 강아지 문이 자동으로 닫힌다. - 3

1번은 하드웨어 개발자가 알아서 할 일이다. 2번은 이미 해결을 한 이슈이다. 3번은 고갱님이 새로 요구해서 추가해야하는 기능이다. N/A 들은 적용할 필요가 없는 유스케이스들이다.

요구사항 반영 - 자동 문닫기

// Remote.ts
import { DogDoor } from "./DogDoor";
export type Remote = InstanceType<typeof Remote>;
export const Remote = class {
 private _door: DogDoor;
 constructor(door: DogDoor) {
   this._door = door;
}

 public pressButton(): void {
   console.log("Pressing the remote control button...");
   if (this._door.isOpen()) {
     this._door.close();
  } else {
     this._door.open();
     setTimeout(() => {
       this._door.close();
    }, 5000);
  }
}
};

// DogDoorSimulator.ts
import { DogDoor } from "./src/DogDoor";
import { Remote } from "./src/Remote";
export class DogDoorSimulator {
 public main(): void {
   const door: DogDoor = new DogDoor();
   const remote: Remote = new Remote(door);

   console.log("Fido barks to go outside...");
   remote.pressButton();

   console.log("\nFido has gone outside...");

   console.log("\nFido's all done...");

   console.log("\nFido's back inside...");
}
}

new DogDoorSimulator().main();

실행해보면 동작은 잘 한다. 그러나 이것을 고갱님께 보여줘야 한다. 리얼 월드와 코드는 다르기 때문이다. 생각한대로 잘 돌아가면 더할나위 없이 좋겠지만 현실은 그렇지 않다.

실제로 나도 아두이노를 가지고 고양이 급식기를 만든 적이 있다. 물론 급식통을 만드는 일이 더 큰 일이었다. 그리고 리얼 월드에서는 툭하면 고양이가 급식기 죽탱을 쳐서 뒤집어 놓더라 (…)

https://www.facebook.com/groups/codingeverybody/permalink/1815257705181424/

다시 강아지 문으로 돌아와서, 여기서도 6.1부터 6.5까지 대체 경로를 만들어놓지 않았더라면 또 다시 개발을 해야하는 상황이 벌어졌을 것이다.

시뮬레이터를 구현해보자.

import { DogDoor } from "./src/DogDoor";
import { Remote } from "./src/Remote";
export class DogDoorSimulator {
 public main(): void {
   const door: DogDoor = new DogDoor();
   const remote: Remote = new Remote(door);

   console.log("Fido barks to go outside...");
   remote.pressButton();

   console.log("\nFido has gone outside...");

   console.log("\nFido's all done...");

   setTimeout(() => {}, 10000);

   console.log("...but he's stuck outside!");

   console.log("\nFido starts barking...");
   console.log("...so Gina grabs the remote control.");
   remote.pressButton();

   console.log("\nFido's back inside...");
}
}

new DogDoorSimulator().main();

사실 책은 java로 되어있어서 쓰레드 구현을 하지만, Typescript의 경우에는 싱글쓰레드 기반의 언어다보니… 제대로 된 구현은 하지 못한듯 하다.

용어 정리

외부 구동자: 유스케이스에 설명된 단계들의 리스틀를 시작시킨다. 이것이 없으면 유스케이스는 시작되지 않는다.

유스케이스: 좋은 요구 사항들을 수집하게 도와준다. 시스템이 할 일에 관한 이야기를 한다.

시작조건: 이것이 항상 유스케이스의 첫 번째 단계이다.

요구사항: 성공하기 위해 시스템이 해야 할 일

명확한 가치: 이것이 없이는 유스케이스가 아무에게도 소용없다. 이것 없는 유스케이스는 항상 실패한다.

종료 조건: 유스케이스가 언제 끝나는지 알려준다. 이것이 없으면 유스케이스는 끝나지 않는다.

주 경로: 모든 것이 제대로 진행되었을 때의 시스템이 하는 일. 고객이 시스템에 대해 얘기할 때 보통 이것을 얘기한다.

기타 유스케이스

이외에도 다양한 요구사항이 발생할 수 있다.

  • 강아지가 창문을 코로 열고 나간다. 코드 입력시마다 강아지 문, 창문이 잠기는 시스템을 만들어주라.

  • 강아지가 발톱으로 긁으면 열리는 문을 만들어달라.

  • 강아지가 밖에 다녀오면 흙을 뭍혀 들어온다. 버튼 눌러 열어줄 때까지 닫힌 채로 있는 강아지 문을 만들어달라.

다음에는 요구 사항 변경에 대해서 알아보도록 한다.


Comments