717
23
애플리케이션 설계
전략
관련 알고리즘의 모음을 실체화해서 필요에 따라 알고리즘을 바꿔서 사용할 있게 한다.
함수를 사용해서 이를 쉽게 구현할 있다. 예를 들어
map
호출할 호출하는 쪽에서
원하는 대로 실제 원소를 변환하기 위해 사용하는 ‘알고리즘’을 지정할 있다.
템플릿 메서드
변하지 않는 메서드로 알고리즘의 뼈대를 정의하고, 그 메서드에서는 서브클래스에서 오버
라이딩함으로써 동작을 변화시킬 있는 다른 메서드를 호출한다. 이 패턴은 구체적 메서드
오버라이딩하는 것보다 훨씬 안전하며, 일관성 있기 때문에 내가 가장 좋아하는 패턴
하나다. 이에 대해서는
11
.
1
.
1
절 ‘구체적 멤버를 오버라이딩하는 일 피하기에서 설명했
다. 오버라이딩하기 위한 추상 메서드를 정의하는 것의 대안은 고차 함수로 템플릿 메서드를
정의하템플릿 메서드가 전달받은 함수가 필요한 커스텀화를 수행하게 하는 것이다.
비지터
절차를 인스턴스에 넣어서 다른 코드에서 해당 타입이 지원하지 않는 연산의 내부에 접근할
있게 해준다. 이 패턴은 공개 인터페이스를 해킹하고 구현을 복잡하게 만들기 때문에 끔찍
패턴이다. 다행히 우리에게는 훨씬 나은 대안이 있다. 타입을 설계하는 사람은
unapply
unapplySeq
메서드를 사용해서 적절한 내부 상태만 외부에 노출시키는 부가 비용이 적은
절차를 정의할 있다. 패턴 매칭은 기능을 사용해서 값을 뽑아내고 기능을 정의할 수
있다. 타입 클래스는 기존 타입에 새로운 동작을 추가하는 다른 방법이다. 다만 일부 특별
경우 필요한 내부에 대한 접근은 타입 클래스로 제공할 없다. 물론 내부 상태를 그런
으로 접근해야 한다는 것은 심각한 설계 냄새 하나다.
23.5
계약에 의한 설계를 활용해서 더 좋게 설계하기
우리가 사용하는 타입은 프로그램이 허용하는 상태에 대해 기술한다. 테스트 주도 개발
Test
-
Driven
development
(
TDD
)이나 다른 테스트 접근 방식을 활용해서 타입이 지정할 없는 동작을 검증할
있다.
TDD
함수형 프로그래밍이 주류에 편입되기 훨씬 전에 버트란드 메이어
Bertrand
Meyer
계약에 의한 설계
Design
by
Contract
(
DbC
)라는 접근 방식을 설명하고, 에펠
Eiffel
언어(
http
://
718
4
고급 주제 및 실전 응용
bit
.
ly
/
10FhJln
)에 그것을 구현했다. 그 아이디어는 사람들의 관심에서 멀어졌지만, 클라이언
트와 서비스 사이의 계약이라는 개념을 중심으로 만들어진 새로운 구현이 생겨났다. 이는 설계
대해 생각할 매우 유용한 은유다. 우리는
DbC
라는 용어를 주로 사용할 것이다.
어떤 모듈의 계약’은 가지 조건을 지정할 있다.
1
. 모듈이 제대로 서비스를 제공하기 위해 모듈에 들어오는 입력에 어떤 제약이 있어야 하는
가? 이런 제약을 전제조건
precondition
이라고 부른다. 서비스가 순수함수로 작하지 않는
다면, 제약조건에 시스템의 요구 사항과 외부 데이터를 함께 명시해야 것이다. 전제
조건은 클라이언트가 있는 일을 제한한다.
2
. 전제조건이 만족된 경우, 모듈이 보장하는 결과에 대해 어떤 제약조건이 존재하는가?
일컬어 후행조건
postcondition
이라고 하며, 서비스를 제한한다.
3
. 서비스를 호출하기 전과 호출한 다음에 어떤 불변조건
invariant
참이어야 하는가?
추가로, 계약에 의한 설계에서는 이런 계약상의 제약 사항을 실행 코드에 지정하도록 요구한
다. 따라서 실행 시점에 제약 사항을 자동으로 강제할 수 있다. 어떤 조건이 실패하는 경우
스템은 즉시 중단되며, 여러분은 조건을 왜 만족시키지 못했는지 즉시 검토해서 수정해야
다. (나는 팀의 리더가 이런 갑작스러운 종료가 ‘불편하다’고 결정하기 전까지는
DbC
를 성공
적으로 사용한 프로젝트를 수행한 적이 있다. 몇 달 후 계약 실패로 로그가 꽉 찼지만 아무도
이상 원인을 찾아 수정하려 하지 않았다. )
이런 제약조건은 실제 서비스가 아니라 개발 테스트 단계에서만 검증하는 것이 일반적이
다. 그렇게 하면 추가 부가 비용을 없앨 있고, 조건을 만족하지 못할 경우 실제 시스템이 중단
되는 사태도 피할 수 있다. 액터 모델에 문제가 생기면 중단시키기
let
it
crash
정책은 이런 개념과
일맥상통한다. 실행 시점에 어떤 조건을 만족시킬 없다면, 부분을 중단시키고 런타임이
구를 실행하도록 해야 하지 않을까?
스칼라는 계약에 의한 설계를 명시적으로 지원하지 않는다. 하지만
Predef
(
http
://
bit
.
ly
/
1086O2z
)에 있는
assert
,
assume
,
require
와 같은 메서드를 이런 목적에 사용할 수 있다.
다음 예제는 계약을 강제하기 위해
require
assert
사용하는 방법을 보여준다.
719
23
애플리케이션 설계
//
src
/
main
/
scala
/
progscala2
/
appdesign
/
dbc
/
BankAccount
.
sc
case class Money
(
val amount
:
Double
)
{
//
require
(
amount
>
=
0
.
0
,
s
"
Negative amount
$
amount not allowed
")
def
+
(
m
:
Money
):
Money
=
Money
(
amount
+
m
.
amount
)
def
-
(
m
:
Money
):
Money
=
Money
(
amount
-
m
.
amount
)
def
>
=
(
m
:
Money
):
Boolean
=
amount
>
=
m
.
amount
}
case class BankAccount
(
balance
:
Money
)
{
def debit
(
amount
:
Money
)
=
{
//
assert
(
balance
>
=
amount
,
s
"
Overdrafts are not permitted
,
balance
=
$
balance
,
debit
=
$
amount
")
new BankAccount
(
balance
-
amount
)
}
def credit
(
amount
:
Money
)
=
{
//
new BankAccount
(
balance
+
amount
)
}
}
➊ 돈을 캡슐화한다. 전제조건으로
require
를 사용해서 양의 정수만 허용한다(상용 환경에서
실행에 대해서는 본문의 설명을 참조하라 ).
➋ 계좌 잔고가 음수가 되지 못하게 한다. 실제로 이는
BankAccount
의 불변조건이다. 그래서
여기서는
require
대신
assert
사용했다.
적어도 여기처럼 단순하고 트랜잭션 등의 다른 요소가 개입하지 않는 경우에는 계약 위반이
나타나지 않을 것이라고 생각된다.
이를 다음 스크립트로 시험해볼 있다.
import scala
.
util
.
Try
Seq
(
-
10
,
0
,
10
)
foreach
(
i
=
>
println
(
f
"$
i
%
3d
:
${
Try
(
Money
(
i
))}"))
val ba1
=
BankAccount
(
Money
(
10
.
0
))

Get 프로그래밍 스칼라: 실용적인 스칼라 활용법을 익히는 가장 확실한 실전 바이블 (2.11.x 버전 기반) now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.