제네릭 없는 PHP 인터페이스

PHP를 사용하면서 가장 아쉬운 부분은 인터페이스다. PHP는 인터페이스를 지원하고 있고 이 인터페이스를 활용한 타입 힌트, 의존성 주입 등 다양한 방식으로 적용 가능하다. 하지만 제네릭 타입이 존재하지 않아서 타입 컬렉션 같이 재사용하기 좋은 인터페이스를 만들 수 없다.

물론 이 문제를 해결하기 위한 패키지를 찾아보면 존재하긴 한다. 하지만 인터페이스가 아닌 클래스 구현에 의존하고 있어서 타입 검사가 로직 내부에 포함되어 있다. 간략한 구현을 보면 대략 다음과 같다. 1

<?php
class Collection
{
    protected $typeName = null;
    protected $collection = [];
    public function __construct($typeName)
    {
        $this->typeName = $typeName;
    }
    public function add($item) {
        if (! in_array($this->typeName, class_implements($item))) {
            throw new \TypeMismatchException();
        }
        $this->collection[] = $item;
    }
}

로직 내에 위치한 타입 검사는 런타임에서만 구동되어 실제로 코드가 실행되기 전까지는 문제가 있어도 찾기가 힘들다. 이런 방식의 구현에서 내부적으로는 인터페이스를 사용해서 입력을 검증하고 있지만 결국 메서드의 유무를 확인하는 덕타이핑과 큰 차이가 없어진다. 결과적으로 인터페이스가 반 쪽짜리 명세로 남아있게 된다. 주석을 잘 달아서 다소 모호한 함수 시그니처를 이해하도록 설득해야 한다.

조금 다른 부분이긴 하지만 PHP에서는 Type을 위한 타입이 존재하지 않는 대신 string으로 처리하기 때문에 위 방식조차도 깔끔하게 느껴지지 않는다. 즉, 타입::class로 반환되는 값도 타입이 아닌 문자열이며 메서드 시그니처에 적용할 수도 없다.

물론 매번 인터페이스와 클래스를 작성해서 사용하는 방법도 있겠다.

<?php
interface CollectionVehicleInterface implements CollectionInterface
{
  public function add(VehicleInterface $item);
}

class CollectionVehicle implements CollectionVehicleInterface
{
  public function add(VehicleInterface $item) {
    $this->collection[] = $item;
  }
}

의도대로 인터페이스를 통해 함수의 입력을 명확하게 정할 수 있게 되었다. 인터페이스는 명세를 명확하게 나타낸다. 다만 이런 방식으로는 모든 경우의 수에 대해 직접 작성해야 하는 수고스러움이 있다. 내부 로직은 동일한데 결국 함수 시그니처가 달라지므로 비슷한 코드를 반복해서 작성해야 한다. 이런 문제를 해결하기 위해 제네릭을 활용할 수 있다.

<?hh
namespace Haruair;

interface CollectionInterface<T>
{
  public function add(T $item) : void;
}

class Collection<T> implements CollectionInterface<T>
{
  protected array<T> $collection = [];
  public function add(T $item) : void
  {
    $this->collection[] = $item;
  }
}
?>

hack에서의 제네릭은 항상 함수 시그니처를 통해서만 사용 가능하며 명시적 선언으로 바로 사용할 수 없어 다른 언어의 제네릭과는 조금 다르다. 물론 hack은 다양한 컬랙션을 이미 제공하고 있으며 array에서도 타입을 적용할 수 있다.

요즘 제대로 된 타입 시스템이 존재하는 프로그래밍 언어를 사용하고 싶다는 생각을 정말 많이 한다. 최근 유지보수하는 프로젝트는 제대로 된 클래스 하나 없이 여러 단계에 걸친 다중 배열로 데이터를 처리하고 있다. 배열에서 사용하는 키는 전부 문자열로 관리되고 있어서 키가 존재하지 않거나 잘못된 연산을 수행하는지 판단하기 어렵다. 어느 하나 타입을 통해 자료를 확인하는 법이 없어 일일이 값을 열어보고 확인하고 있다. 물론 지금 프로젝트의 문제가 엉성한 타입에서 기인한다고 보기에는 다른 문제도 엄청 많다. 그래도 PHP에 타입이 존재하는 이상 조금 더 단단하게 사용할 수 있도록 만들었으면 이런 상황에 더 좋은 대안을 제시할 수 있지 않았을까 생각이 든다.

PHP RFC를 보면 기대되는 변경이 꽤 많이 있는데 빈번히 통과되지 않는 기능이 많아 참 아쉽다. 이 제네릭의 경우도 그 중 하나다. 기왕 인터페이스도 있는데 이런 구현도 함께 있으면 좋지 않을까. 정적 타입 언어도 아닌데 너무 많은 것을 바라는건가 싶으면서도 인터페이스도 만들었으면서 왜 이건 안만들어주나 생각도 동시에 든다. 이렇게 딱히 대안 없는 불평글은 별로 쓰고싶지 않다 ?

  • 이 코드는 실무에서 사용하기 어렵다. 가령 class_implements는 문자열로 전달한 경우에는 해당 문자열을 사용해 클래스 또는 인터페이스를 찾으므로 객체임을 확인하는 코드가 필요하다. 
  • 김용균

    안녕하세요, 김용균입니다. 문제를 해결하기 위해 작고 단단한 코드를 작성하는 일을 합니다. 웹의 자유로운 접근성을 좋아합니다. 프로그래밍 언어, 소프트웨어 아키텍처, 커뮤니티에 관심이 많습니다.

    이 글 공유하기

    이 글이 유익했다면 주변에도 알려주세요!

    페이스북으로 공유하기트위터로 공유하기링크드인으로 공유하기Email 보내기

    주제별 목록

    같은 주제의 다른 글을 읽어보고 싶다면 아래 링크를 확인하세요.

    June 13, 2017

    테스트 주도 개발 : Test-Driven Development by Example

    예전에도 테스트주도개발에 관한 글을 인터넷에서도 한참 찾아보고 읽었었다. 글을 읽고서 TDD를 행동으로 옮겨보면 대부분 글이 구호만 잔뜩 나열했지 무슨 일을 어떻게 해야 하는지 과정을 제대로 설명하는 경우가 거의 없었다. 나도 중요하다고는 늘 이야기…

    May 10, 2017

    왜 클래스죠?

    최근 아키텍처에 관한 책을 읽고 있는데 레퍼런스로 나온 글 중 하나로 Hadi Hariri 의 글 Refactoring to Functional–Why Class? 을 번역했다. 이 글은 함수형으로 리펙토링하기라는 코틀린 연재 중 일부라서 그다지…