Разработка

  • наука
  • ремесло

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. Не используйте else
  3. Оберните все примитивные типы
  4. Коллекции первого класса
  5. Одна точка на строку
  6. Не используйте сокращения
  7. Сохраняйте сущности короткими
  8. Никаких классов с более чем 2 атрибутами
  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 level


public function addRecord(int $level, string $message, array $context = array()): bool
                

public function isHandling(array $record): bool
{
    return $record['level'] >= $this->level;
}
                

Log level


public 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(', ', array_keys(static::$levels)));
    
    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 level


class 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 = array(), DateTimeZone $timezone = null)
     {
         parent::__construct($name, $handlers, $processors);


        // BC compatible date time
        $this->dateTimeProcessor = new DateTimeProcessor($timezone);
        $this->pushProcessor($this->dateTimeProcessor);
     }
                

FilterHandler


-class 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;
    }
                

GeneralHandler


abstract 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