Тестовое задание в 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, стоит сделать формирование ключа более надежным, например пропустив какой-то ключевой элемент массива через функции хеширования