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


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

The post 제네릭 없는 PHP 인터페이스 appeared first on Haruair.