공부 메모: 프로시저 프로그래밍 vs 객체지향 프로그래밍

엽토군·2021년 3월 29일
1

함수 위주의 프로시저 프로그래밍("절차적 프로그래밍")을 해도 될 텐데 굳이 객체지향으로 프로그래밍해야 할 이유가 있다면 무엇일까?

예전에 답 못 했던 질문인데 이번에 객체지향 관련 이론서를 하나 사서 읽는 중이므로 이참에 좀 정리해보려고 글을 판다.


프로시저 프로그래밍에서 '데이터는 데이터일 뿐'이다. 데이터를 받아서 데이터를 처리하고 데이터를 내보내기만 하면 프로시저 프로그래밍은 성립한다. 그런데 그게 프로그래밍의 전부는 아니다 이거지. 거기서 객체 지향 프로그래밍의 아이디어가 도움이 된다. 특정한 유형/목적/종류의 데이터를 특정한 목적/의도 달성을 위해 전담하고 있는 '담당자'가 있다면, 그 담당자에게 좀더 많은 것을 물어보거나 더 많은 책임을 지우거나 할 수 있게 된다.

이런 함수가 있다고 하자.

/**
 * @param integer $a
 * @param integer $b
 * @return integer
 */
function foo($a, $b)
{
    return $b > 0 ? $a - $b : $b;
}

이 함수의 용도가 뭔지 알겠는가?

이것만 보아서는 죽었다 깨나도 모를 것이다. 그리고 이런 함수가 프로덕트 여기저기에서 핵심적으로 쓰이고 있다면, 그 프로덕트를 관리하는 개발자는 입사 첫날부터 탈모에 시달리게 된다. 아마 이대로는 안 되겠다 싶어서, 누가 시키지도 않았는데 시간과 공을 들여 서비스 전체를 분석한 뒤, 이 서비스의 본질이 자판기라는 것을 알아내어, 이런 식으로 '리버스 엔지니어링'을 하게 되겠지.

/**
 * 투입한 금액과 상품 가격을 가지고 잔돈을 반환한다.
 *
 * @param integer $payment
 * @param integer $price
 * @return integer 이 값이 0보다 클 때만 잔돈으로 반환할 것!! 이 함수 리턴값이 음수일 경우 잔액 부족 처리로 넘어가야 함
 */
function getChange($payment, $price)
{
    return $price > 0 ? $payment - $price : $price;
}

그러면 이제 이 함수는 정녕 쓸만해졌는가? 이제 이 절차지향적 프로그램은 온전히 유지 보수 가능한가?

별로 그렇지 않다. 일단 이 함수는 여전히 거의 쓸모가 없달까 잘못된 쓸모를 갖고 있다. 실제로는 그저 크기 비교 한 번과 뺄셈 한 번으로 이루어진 함수임에도 불구하고, 이 함수는 자판기라는 전체 서비스 로직의 코어 ― "잔돈이 부족하면 처리를 중단해야 한다" ― 를 담당하고 있고 그 책임이 막중하다.
그래서 함부로 바꿀 수가 없게 된다. 아무리 봐도 $payment >= 0 && $price >= 0인데 그런 데이터를 입력받은 함수의 결과가 음수일 수 있다고 하는 것은 정말이지 납득하기 어려운 것이다. 하지만 어쩌겠는가? 결과적으로는 이 부조리를 어떻게든 납득한 위에 다른 이런저런 처리를 덧대는 결정을 내리기가 아주 쉽다.

그리고 이 함수는 너무 많은 쓸모를 갖고 있다. 그저 크기 비교 한 번 한 다음 뺄셈 한 번 하는 함수 아닌가 말이지. 그래서 그런 처리가 필요한 경우(이를테면... 시험 성적 집계 같은 거 할 때라든가?)에 갖다 쓰게 될 여지가 충분하다. 특히 그 이름이 getChange()로 바뀌기 전이었다면 더욱더 그렇다. 그 프로그램도 정상 동작을 개런티하면서 foo()getChange()로 바꾸자고? 머리털이 아니라 머리가 빠질 일이다.

애초에 이런 함수를 사용해서 자판기를 구현하는 것은 어딘가 잘못되었다. 결국 이딴 식으로 코딩하게 되거든.

function getPrice($productName)
{
    $products = [
        'coke'  =>  90,
        'pepsi' => 100,
    ];
    return isset($products[$productName]) ? $products[$productName] : 0;
}

$payment = (int) $_POST['payment'];
echo $payment."원 투입하셨습니다.<br>";

$product = $_POST['product'];
$price = getPrice($product);
echo $product." ($price 원) 상품을 선택하셨습니다.<br>";

$change = getChange($payment, $price);
if ($change >= 0) {
    echo "$product 상품을 드립니다. 거스름돈 $change 원을 받아가세요.";
} else {
    echo "투입금액이 부족합니다.";
}

딱 봐도 나중에 골치 깨나 썩일 것이 뻔한 코드이다. 지금이야 잘 돌지 모르지만...

  • 사용자가 상품명으로 "water" 같은 걸 입력했을 때 이 자판기는 받은 돈을 전부 그대로 토해내면서 물을 받아가라고 할 것이다. 이 자판기에 그런 상품은 없는데도.
  • 어떤 상품을 샀을 때 조건부로 "ice"라는 0원짜리 상품을 추가 구매할 수 있다고 할 경우, 탈모에 시달리던 그 개발자는 사직서 작성을 시작하게 된다. getChange()만 가지고 그런 걸 개발하자는 건 너무나 부당한 요구이므로.

그러면 이걸 객체지향적으로 하자고 하면 어떻게 하게 되는가? 뭐 간단한 얘기다. 자동판매를 할 상품들이 객체로 있어야 하고, 그것들을 잔액과 함께 통제할 자판기가 객체로 있으면 된다.

$product = new VendableProduct($_POST['product']);
$machine = new VendingMachine();
$machine->addBalance((int) $_POST['payment']);
$machine->addSelect($product);
if (!$machine->hasEnoughBalance()) {
	return ['error' => '잔액이 부족합니다.'];
}
return [
	'message' => '결제 완료되었습니다.',
	'products' => $machine->getSelectedProducts(),
	'change' => $machine->getChange(),
];

핵심은 객체 지향적으로 개발할 때 데이터는 그냥 데이터가 아니라 그 객체의 속성이 된다는 것이다. 예컨대 위 코드에서 new VendableProduct('coke')가 실행이 됐다면, "coke"는 단순한 문자열 형의 값(value)이 아니라, 어떤 VendableProduct 인스턴스의 "코드명"을 "coke"로 규정하는 유의미한 정보가 된다. 그 자판기 상품이 고유하게 갖는 성질 ― '속성(attribute)' ― 인 것이다.

그리고 그 덕분에, 주어진 코드명으로부터 상품 가격이나 '얼음을 공짜로 받을 수 있는 상품인지' 여부를 계산한다든가 $machine이 갖고 있는 $balance, $selects 등을 검사해서 처리한다든가 하는 게 훨씬 더 체계적이고 조직적으로 구현된다. 무엇보다도, 객체(인스턴스)마다 이름이 있으니, 지금 무슨 일이 일어나고 있는지가 바로 보인다는 굉장한 부가적 이점도 있고 말이다.

이걸 함수 여러 개 도입해서 처리하자면 어떻게 될까? 아마 if ... else if ... else ... 지루박을 타면서 산으로 가겠지.


프로시저 프로그래밍은, 다음과 같은 경우에 유의미할 수 있다.

1. 서비스가 DB 내 "저장 프로시저" 위주로 굴러가고 있을 경우.
프로시저를 쓰는 서비스라면 프로시저 프로그래밍을 해야지 별수 있나.

2. 데이터에 정해진 속성을 부여하면 안 되는 경우.
예컨대 이런 테이블은 class Order 같은 ORM 모델 만들기가 매우 까다롭다. 왜냐하면 ORDERS.product_no가 의미하는 값이 ORDERS.product_table에 따라서 달라지기 때문이다.

CREATE TABLE ORDERS (

	-- 이 테이블에 연관된 상품 테이블이 여러 개 있어서...
	product_table varchar(100) COLLATE Korean_Wansung_CI_AS NULL,
    
	-- 그 중 한 테이블의 특정 자료와 연관하는 구조일 때
	product_no smallint NOT NULL, /* 생략*/
)

3. 데이터가 정말 데이터일 뿐일 경우. 애매한 용도로 설계된 테이블들로 가득찬 DB를 애매하게 공유하는 갖가지 서비스 구현체가 각자의 딕셔너리를 가지고 제멋대로 개발돼 있을 때가 보통 이렇다. 그때는 오직 DDL만이 유일하게 합의 가능한 대상이므로, 데이터를 varcharchar(10)이냐 하는 수준과 범위에서만 매우 조심스럽게 (그리고 매우 낮은 생산성 안정성 일관성과 함께) 다루게 된다.


오개념이나 문제가 있을시 수시로 업데이트 예정

profile
4년차 PHP 개발자입니다.

0개의 댓글