공부하는 블로그

#6 - 요구사항 변경 본문

design patterns

#6 - 요구사항 변경

devtimothy 2019. 3. 26. 21:24

Head first OOAD를 타입스크립트로 읽기 #6 - 요구사항 변경

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

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

만든 프로그램이 잘 동작하지만, 고갱님들은 잘 동작하는 것에만 만족하지는 않는다. 기능을 만들어주면 추가적으로 더 원하는 것이 생긴다.

개발자들이 고갱님의 전화를 받으면 보통은 '문 닫히는 속도가 느린가?', '리모콘 버튼이 이상한가?' 등의 걱정을 먼저 하지만, 그런 것이 아니다. 고객들은 우리가 상상하는 것 이상으로 게으르다. 고객은 개가 나가고 싶다고 짖는 소리에 신경써야 하는 것도 귀찮아하게 되었다. 짖는 소리를 못 들어서 개가 바닥에다가 지리는 (…) 사건도 있단다. 리모콘을 못 찾으면 또 난감한 경우도 있다.

그래서 이제는 개가 짖으면 문이 자동으로 열리게 해달라는 요청을 받았다.

개발자 입장에서는 아놔 그냥 쓰지 라고 생각할 수 있겠지만, 우리는 고갱님의 기대에 부응해야 하는 개발자이다. 코드를 잘 짜놓으면 요구사항이 어떤 것들이 들어오더라도 오히려 돈 벌수 있는 기회라 여기고 기뻐하게 될 것이다.

소프트웨어 분석, 설계의 불변의 법칙

소프트웨어 분석, 설계의 변하지 않는 한가지 진리는 소프트웨어는 변한다. 라는 사실이다.

내 경우에도 음식점 대기 시스템을 만들면서 알림톡 기능을 개발하며 요구사항이 자주 변했다. 초기에는 대기 등록시에만 알림톡이 갔는데, 이제는 알림톡 종류가 점점 더 늘어나고, 우리 서비스 이름이 아니라 음식점의 이름으로 알림이 가게 하고 싶다는 경우도 생기게 되었다. 그에 따라서 알림톡 템플릿 관리를 위해 기능 추가가 필요한 상황이 일어나기도 하였다.

어떻게 하면 항상 변하는 고객의 요구사항을 빠르게 반영할 수 있을까? 유스케이스가 잘 만들어져있다면 소프트웨어를 빠르게 바꿀 수 있을 것이다.

책을 읽다보니 이 유스케이스를 테스트 코드로 삽입하는 것이 좋은 것일까? 라는 생각이 들기도 한다. 사실 문서화라는게 중요하다는 것은 잘 알면서도 못하는 부분이지 않는가.

자, 이제 강아지 문의 유스케이스에 고객의 요구사항을 반영시켜보자.

요구사항

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

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

    1. 강아지 소리 인식기가 강아지 소리를 듣는다.

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

    1. 강아지 소리 인식기가 강아지 문이 열리도록 신호를 보낸다.

  4. 강아지 문이 열린다.

  5. 개가 밖에 나간다.

  6. 개가 밖에서 쉬한다.

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

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

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

      1. 강아지 소리 인식기가 강아지 소리를 듣는다.

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

      1. 강아지 소리 인식기가 강아지 문이 열리도록 신호를 보낸다.

    5. 강아지 문이 열린다.

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

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

2-1, 3-1, 6-3-1, 6-4-1은 사실 대체 경로를 나타낸다. 문서가 다른 사람들이 보아도 한번에 알아볼 수 있도록 잘 작성하는게 좋겠다.

대체 경로란 선택적이거나 대체할만한 방법을 나타내는 단계이다. 주 경로에 추가될 수도 있고, 주 경로와 다르게 갈래를 만들 수도 있다. 이는 여러 갈래로 갈라질 수도 있다. 대체 경로는 필요 없는 기능이 아니며, 유스케이스에 꼭 필요한 존재이다.

요구 사항 추리기

경로를 분할해서 살펴보자.

주 경로

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

  2. 강아지 소리 인식기가 강아지 소리를 듣는다.

  3. 강아지 소리 인식기가 강아지 문이 열리도록 신호를 보낸다.

  4. 강아지 문이 열린다.

  5. 개가 밖에 나간다.

  6. 개가 밖에서 쉬한다.

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

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

    3. 강아지 소리 인식기가 강아지 소리를 듣는다.

    4. 강아지 소리 인식기가 강아지 문이 열리도록 신호를 보낸다.

    5. 강아지 문이 열린다.

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

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

대체 경로

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

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

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

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

위 경로에서 굵은 글씨로 표시한 것들이 우리가 새로 개발할 기능들이다.

코드 작성

// BarkRecognizer.ts
import { DogDoor } from "./DogDoor";
export type BarkRecognizer = InstanceType<typeof BarkRecognizer>;
export const BarkRecognizer = class {
 private _door: DogDoor;
 constructor(dogDoor: DogDoor) {
   this._door = dogDoor;
}
 public recognize(bark: string): void {
   console.log(`BarkRecognizer: Heard a ${bark}`);
   this._door.open();
 }
};

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

   console.log("Fido barks to go outside...");
   recognizer.recognize("Woof");

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

   setTimeout(() => {
     console.log("...but he's stuck outside!");

     console.log("\nFido starts barking...");
     recognizer.recognize("Woof");

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

new DogDoorSimulator().main();

이렇게 코드를 작성해 보았다. 그런데 위 코드에서는 강아지 문이 자동으로 닫히지 않는다. 리모콘에서 한 것 처럼 BarkRecognizer에 똑같은 타이머 코드를 추가하면 될까? 이는 개발자의 자존심이 허락하지 않는다. DRY 원칙을 기억하자.

기능을 어디에 추가할 것인가? 문을 닫는 것은 DogDoor의 역할이 아닐까? 생각하여 설계를 하였다.

// DogDoor.ts
export type DogDoor = InstanceType<typeof DogDoor>;
export const DogDoor = class {
 private _open: boolean;

 constructor() {
   this._open = false;
}

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

 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();
  }
}
};

위와같이 코드를 짬으로서 역할 분리가 온전히 이루어지게 되었다. (이를 객체지향의 원리 중 '캡슐화'라고 부른다.) 좀 더 나아가서, 강아지 문이 자동으로 닫히는 시간을 주인이 설정할 수 있게 한다면 어떻게 해야할까? 생각해보자.

요구사항을 변경하던 중에 우리가 알지 못하는 기존 문제를 발견하게 되기도 한다. 변경은 지속적으로 일어나며, 우리 시스템은 우리가 변경할 때마다 항상 개선되어야 한다.



Comments