Top Banner
PHP: прагматичный код Interlabs 7 марта 2014 1 / 36
36

PHP: прагматичный код

Jun 16, 2015

Download

Technology

Материалы очередного семинара посвящены стилю кодирования, принятому в нашем новом корпоративном фреймворке:

- как структурировать код;
- как называть классы;
- как классифицировать и обрабатывать ошибки;
- как документировать;
- какие языковые конструкции использовать для того, чтобы упрощать, а не усложнять код;
- как минимизировать ошибки в коде.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: PHP: прагматичный код

PHP: прагматичный код

Interlabs

7 марта 2014

1 / 36

Page 2: PHP: прагматичный код

О чем речь

Какими правилами нужно руководствоваться, чтобы писатьпростой и удобный в сопровождении PHP-код:

• как структурировать код• как называть классы• как классифицировать и обрабатывать ошибки• как документировать• какие языковые конструкции использовать для того, чтобыупрощать, а не усложнять код

• как минимизировать ошибки в коде

2 / 36

Page 3: PHP: прагматичный код

Пространства имен• весь код в классах;• все классы — в пространствах имен;• код вне пространств имен — только непосредственно ввыполняемых сценариях.

• только автозагрузка классов, никаких явных include;• вложенные пространства имен могут содержать деталиреализации исходного пространства;

• никогда не определяем глобальные функции.

Главное средство структурирования кода

3 / 36

Page 4: PHP: прагматичный код

Простейшая автозагрузка

spl_autoload_register()

• spl_autoload_register() можно вызывать несколькораз, результат — набор загрузчиков

• выполняются все загрузчики, пока не будет загружен класс• простая, быстрая и удобная реализация• для каждой отдельной иерархии имен — свой autoload• однозначно удобнее composer на этапе разработки• не отменяет и не исключает composer и его загрузчик, ноудобно для экспериментов и в случае использованиясобственных библиотек

4 / 36

Page 5: PHP: прагматичный код

Автозагрузка: реализацияlib/ - пространство имен IMP

Common - пространство имен IMP\Common...autoload.php - загрузчик

script.php - выполняемый сценарий

// autoload.phpspl_autoload_register(function ($class) {

if (substr($class, 0, 4) == ’IMP\\’) {return include_once(

__DIR__ . ’/’ .str_replace(’\\’, DIRECTORY_SEPARATOR, substr($class, 4)) . ’.php’

);}

});

// script.phpinclude(’lib/autoload.php’);use IMP\Common\Container;...

5 / 36

Page 6: PHP: прагматичный код

Библиотечные классы• подгрузка функций пространства имен не реализована,функции определяются в классах

• библиотечный класс содержит только static-методы• методы группируются в класс по функциональности• нельзя создать экземпляр или наследовать

final class API // final запрещает наследование{

static public function apiMethod(){

...}

// Приватный конструктор запрещает создание экземпляра.private function __construct() {}

}

API::apiMethod();

6 / 36

Page 7: PHP: прагматичный код

Декларация зависимости ,Всегда явно прописываем зависимости класса через use, дажеесли язык может вывести их сам:

• сразу видны видны все зависимости без необходимостиразбираться в кодe

• облегчается рефакторинг, легче изменять имена классов ипереносить их между пространствами имен

• легко автоматизировать сбор информации о зависимостях• найти все классы, использующие тот или иной класс —просто grep по имени класса

Явные зависимости — вид документирования7 / 36

Page 8: PHP: прагматичный код

Зависимости: примерnamespace IMP\Data;

// DEPENDENCIES /////////////////////////////////////////////////

use IMP\Data\Schema;use IMP\Data\Collecton;

use IMP\Data\Relations\PrimaryRelation;use IMP\Common\Exceptions\InvalidOperationException;

use ArrayAccess;use Countable;use IteratorAggregate;use ArrayIterator;

// CLASS ////////////////////////////////////////////////////////

class Entity implements ArrayAccess, Countable, IteratorAggregate{

...}

8 / 36

Page 9: PHP: прагматичный код

There are only two hard things inComputer Science: cache invalidation andnaming things.

Phil Karlton

Page 10: PHP: прагматичный код

Именование классовклассы, абстрактные классы, интерфейсы

Типы

• не важно, что из себя представляет тип, важно, какуюфункциональность он описывает

• по мере развития проекта интерфейсы могут становитьсяклассами и наоборот

Не нужно вводить специальные правила дляимен интерфейсов и абстрактных классов.

10 / 36

Page 11: PHP: прагматичный код

Именование типов

• первый уровень пространства имен — классы,определяющие базовые понятия модуля, обычноиспользуемые другими модулями

• вспомогательные классы, необходимые для работы одногоиз классов первого уровня — вложенное пространствоимен, соответствующее имени основного класса вединственном числе

• альтернативные реализации класса — вложенноепространство имен, имя класса во множественном числе

• интерфейсы и абстрактные классы не отличаются поспособу именования от классов

11 / 36

Page 12: PHP: прагматичный код

Именование типовМодуль Data:

IMP\Data пространство имен модуляRelation базовый тип отношения, часто — интерфейсTable класс, использующий вспомогательные классы...

IMP\Data\Relations различные виды отношенийAbstractRelation абстрактный базовый классPrimaryRelation дальнейшая иерархия классовSecondaryRelation еще один абстрактный классOneToOneRelation реализация отношенияOneToManyRelation реализация отношения...

IMP\Data\Table вспомогательные классы для класса TableColumnColumnType

12 / 36

Page 13: PHP: прагматичный код

Помним о S.O.L.I.D.• особенно о Liskov Substitution Principle• один класс — одна ответственность• к статическим методам это тоже относится• производные классы наследуют весь функционал, в томчисле и статический

• лучше много мелких классов с разграниченнойфункциональностью

• если наследование от класса не предусмотрено —используем final.

Добавляя в класс очередной метод, думаем,будет ли он иметь смысл в производном классе.

13 / 36

Page 14: PHP: прагматичный код

Базовые интерфейсы

Проектируя очередной класс, прежде всего отвечаем навопрос, какие интерфейсы он должен поддерживать:

• $object->getProperty() — доступ к свойствам• $object->property — доступ к свойствам• $object[$key] — доступ к индексируемым элементам• foreach ($object) — итератор по содержимому• count($object) — размер содержимого• (string) $object — преобразование в строку

14 / 36

Page 15: PHP: прагматичный код

Базовая семантикаНекоторые рекомендации по базовым интерфейсам:

• работа с состоянием через методы доступа• прямой доступ к свойствам как дополнительнаянеобязательная возможность (иначе больше кода иневозможность контролировать интерфейсы)

• набор свойств всегда фиксированный• набор индексируемых элементов, как правило,произвольный

• итератор в отдельном классе обычно лучше, чем егонепосредственная реализация

• преобразование в строку — альтернативноепредставление объекта, а не сериализация

15 / 36

Page 16: PHP: прагматичный код

Работа с простыми типами

Иногда тип проще всего описать простым значением: строкой,числом, массивом. Первое желание сразу написать объектнуюобертку, но:

• в большинстве случаев не надо писать обертку;• достаточно библиотечного класса с процедурным API;• класс может содержать набор констант, описывающихдопустимые значения;

• класс может содержать процедурный API, крайнежелательно наличие функции, контролирующейкорректность значения.

16 / 36

Page 17: PHP: прагматичный код

Использование констант• гораздо меньше ошибок, например, из-за опечаток встроковых константах, можно использовать autocomplete

• нагляднее код• необходимо привязывать к какому-либо типу (autoloader)• технически можно использовать интерфейс илистатический класс

Используем статический класс:

• со временем могут добавиться дополнительные функции• нет наследования — нет последующих проблем с типамипри развитии проекта

17 / 36

Page 18: PHP: прагматичный код

Простые типы: примерfinal class Status{

const HTTP_CONTINUE = 100;...const HTTP_FOUND = 302;

// Проверяет допустимость значения статуса.static public function assert($status){

// проверка допустимости значения статуса}// Возвращает текстовое описание статуса.static public function text($status){

return self::$text[self::assert($status)];}// Проверяет успешность статуса.static public function isSuccessful($status){

return (int) $status >= 200 && (int) $status < 300;}

}18 / 36

Page 19: PHP: прагматичный код

Контроль типов

Чем лучше контролируем типы, тем меньше ошибок:

• всегда указываем типы аргументов в методах иконструкторах классов

• всегда проверяем типы скалярных аргументов илиприводим тип

• для строковых аргументов часто уместнее приведениетипов: можно передавать объекты, приводимые к строке.

19 / 36

Page 20: PHP: прагматичный код

Строковые аргументыПриведение к строке часто удобнее строгой типизации:

class File {public function __toString() {return $this->path;

}public function getStat() { ... }...

}class FS {static public function isWritable($path) {$path = (string) $path;...

}}

FS::isWritable(’data.json’); // метод работает со строкамиFS::isWritable(new File(’data.json’)); // и с объектами

20 / 36

Page 21: PHP: прагматичный код

Обработка ошибок• в базовом API часть функций возвращают код ошибок;• некоторые генерируют исключения;• некоторые позволяют выбирать (например, PDO).

Так жить нельзя:

• унифицируем обработку ошибок за счет использованиеисключений

• для встроенных функций пишем оболочки, еслинеобходимо

• не забываем обрабатывать исключения

Лучше безобразно, но единообразно21 / 36

Page 22: PHP: прагматичный код

Исключения: главноеНеобходимость и специфика типа исключения полностьюопределяется спецификой его обработки:

• прежде чем ввести очередной тип исключений,представьте, кто и как его будет обрабатывать

• нет необходимости описывать каждую возможную ошибкув виде отдельного типа

• диспетчеризация по типу исключения — тип инаследование очень важны

• соответствие уровней абстракции исключения иобработчика тоже важно

• стандартная схема именования с суффиксом Exceptionвполне оправдана

22 / 36

Page 23: PHP: прагматичный код

Встроенные исключенияException

ошибка

LogicExceptionошибка разработчика

• BadFunctionCallException• BadMethodCallException

• DomainException• InvalidArgumentException• LengthException• OutOfRangeException

RuntimeExceptionошибка выполнения

• OutOfBoundsException• OverflowException• RangeException• UnderflowException• UnexpectedValueException

23 / 36

Page 24: PHP: прагматичный код

Logic vs RuntimeLogicExceptionошибка разработчика

• вообще говоря, не должнавозникать, если всенаписано правильно

• стандартный наборисключений для всехмодулей, обработкаоднотипна

• контекст обработки не такуж важен

RuntimeExceptionошибка выполнения

• зависит от внешнихданных, ошибкинеизбежны

• индивидуальныеисключения для каждогомодуля, обработкаиндивидуальна

• локальная обработкавнутри модуля

24 / 36

Page 25: PHP: прагматичный код

Exception__construct($message, $code, Exception $previous)

$message сообщение об ошибке$code код ошибки, используем, если что-то вернуло нам

этот код (PDO, XML, etc)$previous предыдущее исключение: смена уровня

абстракции при обработке исключения

Смена уровня абстракции: если модуль не может обработатьRuntime-исключение, сгенерированное внутри модуля, онгенерирует собственное Runtime-исключение, содержащеессылку previous на исходное.

Чем раньше обрабатывается исключение, тем лучше.25 / 36

Page 26: PHP: прагматичный код

Обработка исключенийtry {

...$this->statement->execute();...

} catch (PDOException $e) {throw new DBIException(

$e->getmessage(),(int) $e->getCode(),$e

);}

$error = null;try {

$xml = smnple_xml_load_file($file, $class, $opts, $ns, $prefix

);} catch (Exception $e) {

$error = $e;}

if ($xml === false || $error !== null) {$xmlErrors = libxml_get_errors();libxml_clear_errors();throw new XMLException(array(

’Error loading XML file %s’, $file),$xmlErrors,$error

);}

26 / 36

Page 27: PHP: прагматичный код

IMP\Common\ExceptionsLogicException

• InvalidKeyException

• InvalidOperationException

• InvalidPropertyException

• InvalidTypeException

• NotImplementedException

RuntimeException

• базовый класс для runtime-исключенийотдельных модулей

• каждый модуль определяет свой классruntime-исключения

• при необходимости — производныеисключения

Базовые классы: форматирование сообщений ипреобразование в строку:

throw new DataException(array(’Unsupported relation type %s:%s’, $selection, $method

));27 / 36

Page 28: PHP: прагматичный код

ЗамыканияКрайне полезны:

• удобный способ коротко описать какое-либо действие• часто позволяют избежать определения классов• позволяют реализовывать простую и логичную архитектуру

Проблемы:

• объект типа Closure, но работать как с объектом нельзя• нельзя (пока) создать с помощью new• нельзя (пока) изменить свойства и добавить свои• привязка this доступна с PHP 5.4

28 / 36

Page 29: PHP: прагматичный код

HTTP-приложение$this->path(’news’, function (Request $r){

$this->path(’daily’, function (Request $r){

$this->match(Pattern::DATE, function (Request $r, $date){

$news = $this[’db’]->select(...);

$this->GET(function (Request $r) use ($news) {

$this->format(’html’, function (Request $r) use ($news) {return new View(’page.news.daily’, array(’news’ => $news));

});

$this->format(’json’, function (Request $r) use ($news) {return $news->asJSON();

});});

});});

});

29 / 36

Page 30: PHP: прагматичный код

Замыкания: шаблоныЗамыкание может содержать не только код, но и разметку:

<?php $this->is(’html’, function ($data, $yield) { ?><div class="page-head"><img src="logo.png" class="page-head-logo">...

</div><ul class="page-menu"><?php foreach ($data[’menu’] as $item) { ?>

...<?php } ?>

</ul><div class="page-content"><?php $yield($data); ?>

</div><?php }) ?>

30 / 36

Page 31: PHP: прагматичный код

Самый главный интерфейс__invoke()

• позволяет сделать любой объект выполняемым• выполняемый объект взаимозаменяем с замыканием• в отличие от замыкания, объект можно долгоконструировать, добавлять дополнительные свойства и т.д.

Если объект конструируется для выполнения одногодействия, есть смысл сделать его выполняемым

Например: текстовый шаблон, правило роутинга и т.д.31 / 36

Page 32: PHP: прагматичный код

HTTP: запросыИногда выполняемый объект и замыкание должны бытьвзаимозаменяемы:

$this->path(’/’, new FrontPage($this))->path(’news’, new NewsController($this))->path(’special’, function(Request $r) {

...});

class FrontPage {

public function __construct(Application $app) {$this->app = $app;...

}public function __invoke(Request $r) {

return new View(’page.front’, ...);}

}

32 / 36

Page 33: PHP: прагматичный код

ШаблонизацияИногда проще всегда использовать объект определенного типа:

class Template {

public function __construct($name) {$this->name = $name;$this->load($name);

}

public function __invoke($data, $yield = null) {...

}}

// Однократная загрузка шаблона ...$t = new Template(’catalog.item’);

foreach ($items as $item) {$t($item); // ... и многократное применение

}

33 / 36

Page 34: PHP: прагматичный код

ДокументированиеПрактически все готовые системы документированияиспользуют формат JavaDoc:

• очень нудно и многословно• много усилий на соблюдение формальностей

Самый простой вариант:

• для каждого метода и класса обязательно однострочноеописание в виде обычного комментария

• по возможности — дополнительное описание в asciidoc.

Качество документации — в полноте описания, а не в формате.

34 / 36

Page 35: PHP: прагматичный код

Документирование: пример// Краткое описание класса//// Подробное описание класса в произвольной форме.// Можно ссылаться на другие классы, например// [class:IMPNamespaceOtherclass]class Test extends BaseClass{

protected $property; // - краткое описание свойства

// Краткое описание метода.//// Подробное описание метода. Можно использовать// *простую разметку*. Можно ссылаться на аргументы,// например, [arg:arg1] и [arg:arg2].public function __construct($arg1, $arg2){

...}

}

35 / 36

Page 36: PHP: прагматичный код

ИтогоСтараемся не делать вещи сложнее, чем они должны быть:

• код — небольшие классы в иерархии пространств имен• явная декларация зависимостей — упрощает рефакторинг• стандарты именования — для удобства, а не для эстетики• типизация, приведение типов если необходимо• исключения для обработки ошибок• новые классы исключений если есть повод ихобрабатывать отдельно

• семантика выполнения — важный элемент архитектуры• замыкания и объекты могут быть взаимозаменяемы• проще формат документирования — больше документации

36 / 36