webcamp 2016: php.Денис Потапов.Рефакторим код не задумываясь
TRANSCRIPT
РАЗРАБОТКАнаукаремесло
REEF KNOT
MONOLOG // Create the logger$logger = new Logger('my_logger');
// Now add some handlers$handler = new StreamHandler('my_app.log', Logger::DEBUG);$logger>pushHandler($handler);
$logger>pushProcessor(function ($record) $record['extra']['dummy'] = 'Hello world!';
return $record;);
// You can now use your logger$logger>addInfo('My logger is now ready');
ОБЪЕКТНАЯГИМНАСТИКАангл. Object Calisthenics.Calisthenics — зарядка,
гимнастика.
читаемостьтестируемостьпонятностьподдерживаемость
ОБЪЕКТНАЯГИМНАСТИКА
1. Только один уровень отступа в методе2. Не используйте else3. Оберните все примитивные типы4. Коллекции первого класса5. Одна точка на строку6. Не используйте сокращения7. Сохраняйте сущности короткими8. Никаких классов с более чем 2 атрибутами9. Никаких геттеров, сеттеров и свойств
9. Никаких геттеров, сеттеров и свойств
ПРЕЗЕНТАЦИЯbit.ly/webcamp16
КОДbit.ly/webcamp16code
#4КОЛЛЕКЦИИ ПЕРВОГО
КЛАССАинкапсуляция поведений, относящихся кколлекции:поискфильтрация
LOGGER/** * The handler stack * * @var HandlerInterface[] */ protected $handlers; public function isHandling(int $level): bool $record = array( 'level' => $level, );
foreach ($this>handlers as $handler) if ($handler>isHandling($record)) return true;
return false;
HANDLERSTACK/** * The handler stack * * @var HandlerStack */ protected $handlers; public function isHandling(int $level): bool return $this>handlers>isHandling($level);
GROUPHANDLER/** * The handler stack * * @var HandlerInterface[] */ protected $handlers; public function isHandling($record): bool foreach ($this>handlers as $handler) if ($handler>isHandling($record)) return true;
return false;
HANDLERSTACK/** * The handler stack * * @var HandlerStack */ protected $handlers; public function isHandling(int $level): bool return $this>handlers>isHandling($level);
LOGGER/** * Processors that will process all log records * * To process records of a single handler instead, add the processor on that specific handler * @var callable[] */ protected $processors;
public function pushProcessor(callable $callback): self array_unshift($this>processors, $callback);
return $this; ...
foreach ($this>processors as $processor) $record = call_user_func($processor, $record);
PROCESSORSTACK/** * The procesors stack stack * * To process records of a single handler instead, add the processor on that specific handler * @var ProcessorStack */ protected $processors;
public function pushProcessor(callable $callback): self $this>processors>push($callback);
return $this;
...
$record = $this>processors>process($record);
PROCESSABLEHANDLERTRAIT/** * Processors that will process all log records * * To process records of a single handler instead, add the processor on that specific handler * @var callable[] */ protected $processors;
public function pushProcessor(callable $callback): self array_unshift($this>processors, $callback);
return $this;
protected function processRecord(array $record) foreach ($this>processors as $processor) $record = $processor($record); return $record;
DRY-30 LOC
-4 теста
FIGURE 8 KNOT
#3ОБЕРНИТЕ ВСЕ
ПРИМИТИВНЫЕ ТИПЫпредсказуемостьинкапсуляция операцийесли у объекта есть поведениеадаптировано для PHP
LOG LEVELpublic function addRecord(int $level, string $message, array $context = array()): bool
public function isHandling(array $record): bool return $record['level'] >= $this>level;
LOG LEVELpublic static function toMonologLevel($level): int if (is_string($level)) if (defined(__CLASS__.'::'.strtoupper($level))) return constant(__CLASS__.'::'.strtoupper($level)); throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode( return $level;
/** * This is a static variable and not a constant to serve as an extension point for custom levels * * @var string[] $levels Logging levels with the levels as key */ protected static $levels = [ self::DEBUG => 'DEBUG', self::INFO => 'INFO',.....
LOG LEVELclass LogLevel implements LogLevelInterface .... public function __toString() $name = array_search($this>level, self::$levels); if ($name !== false)
return strtoupper($name);
return 'undefined'; .... /** * @inheritdocs */ public function includes($level): bool if ($level instanceof LogLevel) return $level>getLevel() >= $this>level;
return $level >= $this>level;
BOWLINE
#2НЕ ИСПОЛЬЗУЙТЕ ELSE
читаемостьпредсказуемостьearly returnзащитное программирование
WEBPROCESSOR public function __construct($serverData = null) if (null === $serverData) $this>serverData =& $_SERVER; elseif (is_array($serverData)) $this>serverData = $serverData; else throw new \UnexpectedValueException('$serverData ....');
WEBPROCESSOR public function __construct($serverData = null) if (null === $serverData) $serverData =& $_SERVER; if (!is_array($serverData)) throw new \UnexpectedValueException('$serverData ....'); $this>serverData =& $serverData;
CLOVE HITCH
#1ТОЛЬКО ОДИН УРОВЕНЬОТСТУПА В МЕТОДЕ
без примера
цикломатическая сложностьодин уровень абстракции
SQUARE LASHING
#8НИКАКИХ КЛАССОВ С
БОЛЕЕ ЧЕМ 2АТРИБУТАМИэто не шутка, а упражнениевысокая связностьинкапсуляция
ПОЧЕМУ ДВА?обслуживают состояние одного атрибутакоординируют две отдельные переменные
LOGGER /** * @var bool */ protected $microsecondTimestamps = false;
/** * @var DateTimeZone */ protected $timezone;
if ($this>microsecondTimestamps) $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), $this>timezone); else $ts = new \DateTime('', $this>timezone);
$ts>setTimezone($this>timezone);
DATETIMEPROCESSOR class DateTimeProcessor /** * @var bool */ protected $microsecondTimestamps = false;
/** * @var DateTimeZone */ protected $timezone;
public function __construct(DateTimeZone $timezone = null) ...
LOGGER protected $dateTimeProcessor;
public function __construct(string $name, array $handlers = array(), array $processors = parent::__construct($name, $handlers, $processors);
// BC compatible date time $this>dateTimeProcessor = new DateTimeProcessor($timezone); $this>pushProcessor($this>dateTimeProcessor);
FILTERHANDLERclass FilterHandler extends Handler implements ProcessableHandlerInterface
use ProcessableHandlerTrait;
/** * Whether the messages that are handled can bubble up the stack or not * * @var Boolean */ protected $bubble;
public function handle(array $record): bool ...
if ($this>processors) $record = $this>processRecord($record);
...
return false === $this>bubble;
GENERALHANDLERabstract class GeneralHandler extends Handler /* * * @var ProcessorStack */ protected $processors;
protected $bubble = true;
public function handle(array $record): bool if (!$this>isHandling($record)) return false; $record = $this>processors>process($record); $this>postProcess($record); return false === $this>bubble;
FILTERHANDLER class FilterHandler extends GeneralHandler
class GroupHandler extends GeneralHandler
class AbstractProcessingHandler extends GeneralHandler
class SamplingHandler extends GeneralHandler
ХОРОШЕЕ ПРАВИЛОи результаты интересные
bubble - один раз-20 LOC-ProcessableHandlerTrait
TRIPOD LASHING
#7СОХРАНЯЙТЕ СУЩНОСТИ
КОРОТКИМИ200 сток в файле (включая docblock)10 методов в классе15 классов в пространстве имен
LOGGER И NEWLOGGERДо Logger - 442 строк кодаПосле Logger - 275 строк кодаПосле NewLogger - 106 строк кода
TRUCKERS HITCH
#9ГЕТТЕРЫ, СЕТТЕРЫ И
СВОЙСТАуказывай, а не спрашивайпринцип открытости/закрытости
ROPE SWING
#5ОДНА ТОЧКА НА
СТРОКУ* *** ДВА -> НА СТРОКУ В PHP
** КРОМЕ ТЕКУЧИХ ИНТЕРФЕЙСОВ
тестируемость (mock-объекты)закон Деметры
#6НЕ ИСПОЛЬЗУЙТЕСОКРАЩЕНИЯ
слишком длинное название метода?слишком часто приходиться вызыватьметод?читаемость
ВОПРОСЫ?bit.ly/webcamp16
bit.ly/webcamp16code
denyspotapov.com