Тестовое задание в SkyEng на PHP разработчика. SkyEng test review for PHP developer.
- Делаю допущение, что код написан под версию php 5, поэтому не оставляю комментарии на счет пропущенных указаний типов, сопутствующих версии 7
- Исхожу из того, что класс
DataProviderнезависимая сущность, которая может использоваться напрямую клиентским кодом, без слоя кеширования, а значит не требует объявленияabstractи закрытия методаget()в доступprotected(об этом в пункте 8) - Пропускаю любые замечания по поводу тестов (их, очевидно, нет)
- Если код работает с каким-то конкретным АПИ, то вероятно, его адрес это константа, но допустим, что инстанс класса конфигурируется каким-то DI контейнером, который заполнит значение поля
$hostиз переменных окружения - Мне не до конца ясна абстракция примера, поэтому я не стану делать замечание про
$host$userи$password, а просто исправлю это в своей реализации на использование интерфейсаPsr\Http\Client\ClientInterface - В условии написано, что разработчик справился с задачей, но кажется в коде забыто сохранение результата в кеш пул
$this->cache->save($cacheItem); - Is it ok to use Russian language in comments? :)
Ревью:
-
Пропущен
phpdocдля класса с пояснениями об обязаностях классаclass DataProvider -
class DataProviderназван неочевидно. Задача класса, взаимодействовать с АПИ, никак не отображена в его названии -
Для свойств класса
DataProviderнеобходимо указать тип вphpdoc. Это поможет тем, кто будет наследоваться от этого класса — IDE укажет предполагаемый тип переменных класса -
Тоже самое для конструктора класса
DataProvider. Вphpdocпропущены типы аргументов -
Метод
public function get(array $request)классаDataProvider- название выбрано неудачно. Нет ясности из сигнатуры метода, что именно получает метод (и что возвращает поphpdoc) -
Из условий задачи и реализации метода
getне совсем понятно как именно используются классы и какие параметры принимют для выполнения запроса, но кажется уpublic function get(array $request)входным аргументом должен быть интерфейсRequestInterface, а не массив неопределенных данных. Тоже самое касается возвращаемого типа. Может этоResponseInterface? -
Класс
class DecoratorManager extends DataProvider. Теже пункты -phpdocи название. Допустимphpdocможно опустить, но из названия не ясно, что делает класс, кроме того, что декорирует что-то -
Исходя из названия
DecoratorManagerможно предположить, что класс задумывался как реализация паттерна декоратор, а значит стоит применить композицию, а не наследование от провайдера АПИDataProvider, что также позволит покрыть обе сущности собственными, независимыми тестами -
В классе
class DecoratorManager extends DataProviderявное нарушение SRP: формирование ключа для кеша можно вынести в отдельную фабрику -
Не вижу ни единой причины свойствам
$cacheи$loggerу классаDecoratorManagerбыть публичными. Так же пропущенphpdocдля обоих свойств -
Ну раз уж есть
public function setLogger(LoggerInterface $logger)то можно сообщить контейнеру (или просто клиентскому коду), который будет инжектировать через этот сеттер зависимость, что классDecoratorManagerимеет эту зависимость и указать реализацию интерфейсаPsr\Log\LoggerAwareInterface. Там же где-то и трейт естьLoggerAwareTrait -
public function getResponse(array $input)как и у родительского класса.. может тут что-то вродеmakeApiRequestилиfetchDataFromApi? -
Аналогичные вопросы к входным и возвращаемым типам у метода
public function getResponse(array $input). Опять же, из задачи не совсем очевидно, но думаю в реальной жизни можно сделать более очевидную сигнатуру с явными типами для исключения ошибок, связанных с массивом:public function getResponse(ApiRequestInterface $request): ApiResponseDataInterface -
(new DateTime())->modify('+1 day')время жизни кеша стоит вынести в константу (или в параметр, который можно передать при создании объекта) -
$this->logger->critical('Error');ну можно еще передать$e->getMessage()для большей наглядности и сообщение изменить на какое-то явное, вродеFailed to fetch data from API -
} catch (Exception $e) {отлавливать корневой эксепшен не очень хорошая практика, но возможно это допустимое решение для данной задачи. Как вариант - разбить на несколько блоковcatchс разным поведением и разным уровнем логирования. Например, ошибки связанные с сетью, пробрасывать выше, чтобы клиентский код сам решал как на них реагировать -
public function getCacheKey(array $input)если этот метод, после рефакторинга, остается в классе, то у него нет причин быть публичным -
return json_encode($input);можно, но ненадежно. При вызовеgetCacheKeyнужно помнить чтоjson_encodeможет вернутьfalseв случае ошибки. Используя фабрику из пункта 9, стоит сделать формирование ключа более надежным, например пропустив какой-то ключевой элемент массива через функции хеширования