공부하는 블로그

#4 - 유지보수와 재사용이 쉬운 디자인을 위해 노력하라 본문

design patterns

#4 - 유지보수와 재사용이 쉬운 디자인을 위해 노력하라

devtimothy 2019. 3. 20. 21:19

Head first OOAD를 타입스크립트로 읽기 #4

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

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

유지보수와 재사용이 쉬운 디자인을 위해 노력하라

진짜, 잘 설계 되었나?

search() 메소드를 다시 한번 살펴보자. 정말 이대로 괜찮은가? 만일 고객이 저는 12줄 기타를 찾는데요! 라고 하면 어떻게 할까? GuitarSpec에 속성을 추가하지만, Inventory 클래스도 함께 수정을 해야 할 것이다. Guitar와 Inventory 클래스는 수정할 필요가 없도록 만드는 것이 좋은 방법이다.

코드 수정

// GuitarSpec.ts
import { Builder } from "./types/Builder";
import { Type } from "./types/Type";
import { Wood } from "./types/Wood";
export type GuitarSpec = InstanceType<typeof GuitarSpec>;
export const GuitarSpec = class {
 private _builder: Builder;
 private _model: string;
 private _type: Type;
 private _backWood: Wood;
 private _topWood: Wood;
 private _numStrings: number;

 constructor(
   builder: Builder,
   model: string,
   type: Type,
   numStrings: number,
   backWood: Wood,
   topWood: Wood
) {
   this._builder = builder;
   this._model = model;
   this._type = type;
   this._backWood = backWood;
   this._topWood = topWood;
   this._numStrings = numStrings;
}

 get builder(): Builder {
   return this._builder;
}

 get model(): string {
   return this._model;
}

 get type(): Type {
   return this._type;
}

 get backWood(): Wood {
   return this._backWood;
}

 get topWood(): Wood {
   return this._topWood;
}

 get numStrings(): number {
   return this._numStrings;
}

 public matches(otherSpec: GuitarSpec): boolean {
   const { builder, model, type, backWood, topWood, numStrings } = this;
   const {
     builder: otherBuilder,
     model: otherModel,
     type: otherType,
     backWood: otherBackWood,
     topWood: otherTopWood,
     numStrings: otherNumStrings
  } = otherSpec;
   if (builder !== otherBuilder) return false;
   if (model && model !== otherModel) return false;
   if (type !== otherType) return false;
   if (backWood !== otherBackWood) return false;
   if (topWood !== otherTopWood) return false;
   if (numStrings !== otherNumStrings) return false;
   return true;
}
};

//Inventory.ts
import { Guitar } from "./Guitar";
import { GuitarSpec } from "./GuitarSpec";
export type Inventory = InstanceType<typeof Inventory>;
export const Inventory = class {
 private _guitars: Guitar[];
 constructor() {
   this._guitars = new Array<Guitar>();
   // this._guitars = [];
}
 addGuitar(serialNumber: string, price: number, spec: GuitarSpec): void {
   const guitar: Guitar = new Guitar(serialNumber, price, spec);
   this._guitars.push(guitar);
}
 getGuitar(serialNumber: string): Guitar {
   return this._guitars.filter(
    (item: Guitar): boolean => item.serialNumber == serialNumber
  )[0];
}
 search(searchSpec: GuitarSpec): Guitar[] {
   return this._guitars.filter(
    (item: Guitar): boolean => {
       return searchSpec.matches(item.spec);
    }
  );
}
};

import { Inventory } from "./Inventory";
import { Guitar } from "./Guitar";
import { Builder } from "./types/Builder";
import { Type } from "./types/Type";
import { Wood } from "./types/Wood";
import { GuitarSpec } from "./GuitarSpec";
const FindGuitarTester = class {
 public main(): void {
   const inventory: Inventory = new Inventory();
   this.initInventory(inventory);

   const whatErinLikes: GuitarSpec = new GuitarSpec(
     Builder.FENDER,
     "Stratocastor",
     Type.ELECTRIC,
     6,
     Wood.ALDER,
     Wood.ALDER
  );

   const guitars: Guitar[] = inventory.search(whatErinLikes);
   if (guitars.length > 0) {
     guitars.forEach((guitar: Guitar) => {
       const { builder, model, type, backWood, topWood } = guitar.spec;
       console.log(`Erin, you might like this ${builder} ${model} ${type} guitar:
               ${backWood} back and sides,
               ${topWood} top.
               You can have it for only ${guitar.price}!`);
    });
  } else {
     console.log("Sorry, Erin, we have noting for you.");
  }
}
 private initInventory(inventory: Inventory) {
   inventory.addGuitar(
     "11277",
     3999.95,
     new GuitarSpec(
       Builder.COLLINGS,
       "CJ",
       Type.ACOUSTIC,
       6,
       Wood.INDIAN_ROSEWOOD,
       Wood.SITKA
    )
  );
   inventory.addGuitar(
     "V95693",
     1499.95,
     new GuitarSpec(
       Builder.FENDER,
       "Stratocastor",
       Type.ELECTRIC,
       6,
       Wood.ALDER,
       Wood.ALDER
    )
  );
   inventory.addGuitar(
     "V9512",
     1549.95,
     new GuitarSpec(
       Builder.FENDER,
       "Stratocastor",
       Type.ELECTRIC,
       6,
       Wood.ALDER,
       Wood.ALDER
    )
  );
   inventory.addGuitar(
     "122784",
     5495.95,
     new GuitarSpec(
       Builder.MARTIN,
       "D-18",
       Type.ACOUSTIC,
       6,
       Wood.MAHOGANY,
       Wood.ADIRONDACK
    )
  );
   inventory.addGuitar(
     "76531",
     6295.95,
     new GuitarSpec(
       Builder.MARTIN,
       "OM-28",
       Type.ACOUSTIC,
       6,
       Wood.BRAZILIAN_ROSEWOOD,
       Wood.ADIRONDACK
    )
  );
   inventory.addGuitar(
     "70108276",
     2295.95,
     new GuitarSpec(
       Builder.GIBSON,
       "Les Paul",
       Type.ELECTRIC,
       6,
       Wood.MAHOGANY,
       Wood.MAHOGANY
    )
  );
   inventory.addGuitar(
     "82765501",
     1890.95,
     new GuitarSpec(
       Builder.GIBSON,
       "SG '61 Reissue",
       Type.ELECTRIC,
       6,
       Wood.MAHOGANY,
       Wood.MAHOGANY
    )
  );
   inventory.addGuitar(
     "77023",
     6275.95,
     new GuitarSpec(
       Builder.MARTIN,
       "D-28",
       Type.ACOUSTIC,
       6,
       Wood.BRAZILIAN_ROSEWOOD,
       Wood.ADIRONDACK
    )
  );
   inventory.addGuitar(
     "1092",
     12995.95,
     new GuitarSpec(
       Builder.OLSON,
       "SJ",
       Type.ACOUSTIC,
       12,
       Wood.INDIAN_ROSEWOOD,
       Wood.CEDAR
    )
  );
   inventory.addGuitar(
     "566-62",
     8999.95,
     new GuitarSpec(
       Builder.RYAN,
       "Cathedral",
       Type.ACOUSTIC,
       12,
       Wood.COCOBOLO,
       Wood.CEDAR
    )
  );
   inventory.addGuitar(
     "6 29584",
     2100.95,
     new GuitarSpec(
       Builder.PRS,
       "Dave Navarro Signature",
       Type.ELECTRIC,
       6,
       Wood.MAHOGANY,
       Wood.MAPLE
    )
  );
}
};

new FindGuitarTester().main();

코드를 보면 search() 메소드에서 직접 비교하던 속성들을 GuitarSpec.matches() 에 위임하였다. 위임이란, 객체가 어떤 일을 할 때 직접하지 않고 다른 객체가 그 일을 하는 것을 말한다. 위임을 통해 우리는 코드의 재사용성을 높일 수 있으며 한 객체의 기능을 여러곳에 분산할 필요가 없다.

위임

위임은 각 객체가 동일성을 스스로 해결하게 한다. 객체가 서로 독립적이고 더 느슨하게 결합되게 한다. 그래서 다른 프로그램에서도 빼내어 재사용하기 쉽다. (느슨하게 결합되었다는 말은 각 객체가 자신의 일만을 수행하는 것을 말한다. 이는 유연하게 코드 수정이 가능하다.)

위임: 한 객체가 기능(operation)을 다른 객체에 넘겨주어 첫번째 객체를 대신해서 수행하도록 하는 행위

OOAD란 (Object Oriented Analysis & Design)

고객은 프로그램이 동작할 때 만족스러워한다.

고객이 원하는 것을 만들어주기 위해 우리는 고객으로부터 요구사항을 얻는다. 유스케이스들과 다이어그램이 도움이 되지만 이는 고객이 원하는 프로그램을 알아내기 위한 것일 뿐이다.

개인 적용: 오늘 같은 경우에는 카카오 Oauth 로그인 기능 구현하는데 구조적으로 짜려는 욕심을 내다가, 클라이언트들이 오랜 시간 기다리는 일이 있었다. 사실 기능이 잘 동작하는지도 모르는 상태에서 구조적인 프로그래밍을 하려다가 동료 개발자분께서 일단 기능 먼저 테스트해보자고 해서 배포 후 기능동작을 확인 후에 리팩토링을 하게 되었다.

고객은 프로그램이 계속 잘 동작할 때 만족스러워한다.

어제까지 잘 되다가 오늘 잘 안되면 좋아할 사람은 없을 것이다. 프로그램을 잘 설계한다면 고객이 이상한 방식으로 조작해도 깨지지 않을 것. 클래스 다이어그램과 시퀀스 다이어그램이 설계의 문제점을 나타내는데 도움을 주지만, 핵심은 잘 설계된 견고한 프로그램을 작성하는 것이다.

고객은 프로그램이 업그레이드가 가능할 때 만족스러워한다.

고객이 간단한 새 기능을 추가하기 원하는데, 2주 걸리고 2500만원이 든다? 좋아하지 않을 것이다. 캡슐화, 구성, 위임의 객체지향 기법을 사용하면 유지보수가 쉽고 확장성이 좋은 프로그램을 만들 수 있다.

개인 적용: 알림톡 메시지 타입을 추가했는데, 기능이 더이상 동작을 안한다면 우리 대표님은 나에게 많은 실망을 하겠지. 알림톡 타입을 추가하면서 나 자신도 불안해하고, 어떻게 기능을 확장해야할지 전전긍긍하며 스트레스 받는다면 해당 기능은 분명 문제가 있는 것이리라. 😭

프로그래머는 자신의 프로그램이 재사용될 수 있을 때 만족스러워 한다.

프로그램을 만들고, 거의 같은 프로그램을 다른 고객에게 제공할 때 사용 불가능 한 경우를 경험해 봤는가? 조금만 분석하면 복잡한 의존 관계와 연관 관계를 피해 쉽게 재사용 할 수 있게 만들 수 있다. (OCP, SRP 등…)

프로그래머는 자신의 프로그램이 유연할 때 만족스러워 한다.

가끔은 약간의 리팩토링을 통해서 좋은 프로그램을 다양한 종류의 일에 사용할 수 있는 훌륭한 프레임웍으로 바꿀 수 있다. (아키텍트로 가는 첫걸음)

핵심 정리

  • 깨지기 쉬운 프로그램은 조금만 잘못 조작해도 문제가 발생한다.

  • 캡슐화와 위임 같은 객체지향 원리를 사용하여 유연한 프로그램을 만들 수 있다.

  • 캡슐화는 프로그램을 여러 개의 논리적 부분들로 나눈다.

  • 위임은 특정한 일을 해결하는 책임을 다른 객체에게 주는 것이다.

  • 프로젝트는 항상 고객이 원하는 것을 알아내는 것부터 시작한다.

  • 프로그램의 기본 기능을 구현한 후에 설계를 우연하게 가다듬는 데 노력하라.

  • 기능과 유연한 설계가 완성이 되면, 디자인 패턴을 사용해서 프로그램의 디자인을 개선하고, 재사용이 용이하게 만든다.

  • 프로그램 중 자주 변경을 요하는 부분을 찾아서 변경되지 않는 부분과 분리해 놓으라.

  • 잘 동작하지만 설계가 엉망인 프로그램의 경우에 고객은 만족시키지만 문제를 고치느라 수고, 고통, 밤샘 등을 겪을 가능성이 크다.


Comments