Заметки об «индийском» коде
- Автор(ы): Чобиток Василий
Содержание
Пример № 1 (PHP 5)
- 14.08.2010
Задача: известно, что путь к картинке хранится в переменной $image_path. Картинка может быть в одном из форматов, который поддерживается браузерами (JPEG, GIF и т. п.). Необходимо картинку передавать в браузер не прямой ссылкой, а при обращении к скрипту.
Естественно, что при получении картинки скриптом в начале PHP-скрипт должен сообщить в заголовке тип передаваемых данных, например: «Content-type: image/jpeg
».
Программист решает эту задачу следующим образом:
$extension = strtolower(strrchr(basename($image_path), '.'));
switch($extension) {
case '.jpg': header('Content-type: image/jpeg'); break;
case '.jpeg': header('Content-type: image/jpeg'); break;
case '.png': header('Content-type: image/png'); break;
case '.gif': header('Content-type: image/gif'); break;
}
В этом коде проблемы следующие:
- Привязка к расширению. Не всегда расширение соответствует реальному формату данных. Дай файлу в формате JPEG расширение «.gif» и этот код перестанет работать. Впрочем, это некритичная проблема, т.к. любой сайтовладелец, должен следить за адекватностью контента и данная проблема может никогда и не проявится.
- При появлении новых форматов изображений (SVG набирает силу) придётся менять код внутри структурного оператора switch и добавлять новые операторы.
Решение проблемы: в PHP есть функции, которые возвращают тип изображения по сигнатуре первых байтов его файла (exif_imagetype), а также Mime-тип изображения по его типу. Рефакторинг предыдущего решения даёт следующий результат:
$imgType = exif_imagetype($image_path);
header('Content-type: ' . image_type_to_mime_type($imgType));
Здесь картинки работают независимо от расширения, нет привязки к ограниченному списку форматов, размер кода сократился в три раза.
Пример № 2 (PHP 5)
- 16.08.2010
Этот пример идет в продолжение предыдущего.
Задача: известен тип ($imgType) изображения, которое хранится по известному пути ($image_path). Необходимо загрузить изображение в память (переменная $img) для последующей программной обработки.
Самый простой способ сделать это следующим образом:
switch ($imgType) {
case IMAGETYPE_GIF:
$img = imagecreatefromgif($image_path); break;
case IMAGETYPE_JPEG:
$img = imagecreatefromjpeg($image_path); break;
case IMAGETYPE_PNG:
$img = imagecreatefrompng($image_path); break;
}
Мы простых путей не ищем и делаем то же самое так:
$imgCreateMethods = array (
IMAGETYPE_GIF => 'imagecreatefromgif',
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
IMAGETYPE_PNG => 'imagecreatefrompng',
);
$img = $imgCreateMethods[$imgType]($image_path);
Хоть кода и меньше, но первый вариант на мой взгляд читается проще. Так зачем в этом случае городить огород?
Суть решения в том, что, как говорилось в предыдущем примере, заранее может быть неизвестен полный перечень форматов, что является изменчивой частью системы. Как утверждают классики, изменчивая часть должна быть инкапсулирована (сокрыта) от основной системы. Поэтому перечень имён функций для создания изображения в памяти вынесен в отдельный массив $imgCreateMethods. Этот массив может быть объявлен во включаемом остальными файлами файле настройки (например, config.php).
Теперь добавление новых форматов или, наоборот, ограничение числа поддерживаемых (если это необходимо) выполняется путем редактирования файла конфигурации, а не файлов с основным функционалом. Файл конфигурации доступен, как правило, не только программисту, но и владельцам сайтов, которые используют этот функционал.
Пример № 3 (SQL, Interbase)
- 25.08.2010
Индусы нервно курят в сторонке. Этот пример SQL-кода отечественного производителя я привожу знакомым в качестве вершины говнокодерского искусства. Ниже дан пример хранимой процедуры в базе данных Interbase. Процедура приведена в сокращенном на несколько сотен строчек виде (см. соответствующие комментарии).
CREATE PROCEDURE STATISTIC_CARD_ZERO_ONE (
causeid integer,
causeorgid integer)
as
declare variable acauseid integer;
declare variable aorgid integer;
declare variable arequested double precision;
declare variable arequestcurrencyid integer;
/*
* Здесь было объявление более 100 переменных,
* которые условно не показаны
*/
declare variable P3_381 date;
begin
for
select
CAUSEID,ORGID,REQUESTED,REQUESTCURRENCYID,
/*... более 100 полей условно не показаны ...*/
P3_36,P3_37,P3_38,P3_381
from
STATISTIC
where
CAUSEID = :CAUSEID and ORGID = :CAUSEORGID
into
:ACAUSEID,:AORGID,:AREQUESTED,:AREQUESTCURRENCYID,
/*... более 100 переменных условно не показаны ...*/
:AP3_36,:AP3_37,:AP3_38,:AP3_381
do
begin
if (AREQUESTED is null or AREQUESTED <> 0) then
begin
update STATISTIC set
REQUESTED = 0
where
CAUSEID = :ACAUSEID and ORGID = :AORGID;
end
if (AREQUESTCURRENCYID is null or AREQUESTCURRENCYID <> 3) then
begin
update STATISTIC set
REQUESTCURRENCYID = 3
where
CAUSEID = :ACAUSEID and ORGID = :AORGID;
end
/* несколько десятков аналогичных блоков if...then не показаны */
if (AP1_84 is null or AP1_84 <> 'F') then
begin
update STATISTIC set
P1_84 = 'F'
where
CAUSEID = :ACAUSEID and ORGID = :AORGID;
end
/* несколько десятков аналогичных блоков if...then не показаны */
if (AJOINED <> '') then
begin
update STATISTIC set
JOINED = ''
where
CAUSEID = :ACAUSEID and ORGID = :AORGID;
end
/* несколько десятков аналогичных блоков if...then не показаны */
if (AP3_381 is not null) then
begin
update STATISTIC set
P3_381 = CAST(null as DATE)
where
CAUSEID = :ACAUSEID and ORGID = :AORGID;
end
end
end
Если не испугаться объема процедуры, то понять смысл в ней происходящего несложно. В имеющейся записи «обнуляются» значения полей, им присваиваются значения по умолчанию. Но как это происходит?! Это происходит вызовом инструкции UPDATE для каждого поля в отдельности (а их — сотни)!!!
Более того, полей в таблице автора сего шедевра настолько много, что со своей процедурой он не влазил в имевшиеся ограничения на объём хранимых процедур, и таких процедур у него несколько. Но тут, тем не менее, стоит отдать должное нашему трудолюбивому кодеру, он написал не обычный говнокод, а оптимизированный говнокод — UPDATE у него вызывается всё же не для всех полей, а только для тех, чьи значения отличаются от значений по умолчанию :-)
Для меня до сих пор остаётся загадкой, почему автору этого творения не пришёл в голову самый простой из возможных вариантов?
CREATE PROCEDURE STATISTIC_CARD_ZERO_ONE (
causeid integer,
causeorgid integer)
as
begin
update STATISTIC
set
REQUESTED = 0,
REQUESTCURRENCYID = 3,
P1_84 = 'F',
/*... более 100 полей условно не показаны ...*/
where
CAUSEID = :CAUSEID and ORGID = :CAUSEORGID
end
Впрочем, этот вариант кода тоже дурно попахивает. Просто аффтар процедуры жжёг нипадецки и, кроме всего прочего, не додумался для полей таблицы объявить значения по-умолчанию.
С нормально объявленными для полей значениями по-умолчанию всё это хозяйство сводится к двум строчкам кода:
delete from STATISTIC where CAUSEID = :CAUSEID and ORGID = :CAUSEORGID;
insert into STATISTIC (CAUSEID, ORGID) values (:CAUSEID, :CAUSEORGID);
Трудолюбивый дурак такое же зло, как и дурак с инициативой. Если человек не привык работать задницей, отсиживая её часами копируя строки кода, и у него есть хоть немного лени, то ваяя подобное остановится и подумает: «А не дурак ли я?»
Пример № 4 (фреймворки)
- 25.07.2012
Уже начинал думать, что мое отвращение к фреймворковой моде нечто вроде технической отсталости от современного прогресса.
И тут вижу в Гугле рекламный блок «PHP для начинающих. Избавим от привычки писать говнокод и фреймворковой зависимости».
Если кто-то фреймворковую зависимость ставит наравне с написанием говнокода, значит и я, оказывается, для этой жизни ещё не потерян :-)
Пример № 5 (шаблонизаторы)
- 26.01.2013
Решил развернуть у себя галереи изображений с использованием движка Piwigo и в очередной раз столкнулся с «птичьим» языком шаблонизатора Smarty.
В предисловии документации по Smarty написано: «после написания нескольких проектов, в которых PHP и HTML свободно перемешиваются, многие понимают, что отделение формы от содержания - это Хорошая Вещь [TM]».
Вроде бы правильная мысль, которая вышла из хорошей идеи отделения друг от друга данных, логики и представления (шаблон проектирования MVC). Однако, в Smarty первоначально верная идея превратилась в бессмысленную идеологию «отделить PHP от HTML». А зачем? Зачем отделять скриптовый язык PHP от HTML с помощью скриптового языка шаблонов Smarty, написанного на самом PHP? Бессмыслица.
По-моему апологеты Smarty в большинстве своем говнокодеры, именно поэтому в качестве примеров «лучшести» Smarty приводят подобный код (подсмотрено на Хабре).
На PHP:
<? if (isset($item['key']):?> <?= $item['key'] ?> <? else: ?> default <?endif ?>
На Smarty:
{$item.key | default:'default'}
Как, оказывается, на PHP много кода и как лаконичен Smarty!
Конечно, что на PHP можно записать короче:
<?=(isset($item['key']) ? $item['key'] : 'default'?>
а в PHP5.3 еще и так:
<?=$item['key'] ? : 'default'?>
говнокодеров на Smarty не волнует.
Ах, да, дело не только в коде. Smarty 3 дает такую колоссальную возможность как наследование шаблонов! Это же так круто! Видимо, говнокодеры на Smarty не подозревают, что в PHP тоже есть наследование, правильное использование которого позволяет прекрасно отделять логику от представления и, переопределяя шаблоны, манипулировать ими для разных страниц без необходимости а) изучать дополнительный синтетический скриптовый язык верстальщику, б) изучать спецификации классов Smarty программисту и в) разворачивать дополнительные монструозные библиотеки, которые по сути не делают ничего нового в сравнении с самим PHP.
Проблема MVC в первую очередь в квалификации разработчика, использование Smarty не прибавит ума и способностей, а только замаскирует проблему и добавит говнокода вместо его минимизации.