Здравствуйте, люди добрые!
Если мы зайдем на сайт говнокод.ру, то мы увидим, что количество постов с плохим кодом в разделе PHP более чем в 3 раза опережает ближайших конкурентов. Естественно, на это есть свои причины — низкий порог вхождения, простота языка, наличие готовых решений, позволяющих не юзать облегчающих решение типичных задач и т.д… Иногда мне даже становится обидно за репутацию языка и остальных php-программистов.
На данный момент я занимаюсь проектированием и внедрением базы данных на одном из предприятий нашего небольшого города. И недавно по рекомендации одной знакомой со мной списался один человек, попросивший просто приделать скрипт отправки почты через форму с небольшой админкой, где можно проследить кто что написал и куда ушло письмо. И в процессе реализации этого скрипта меня посетила мысль, почему плохого кода на php так много.
Условия
Возьмем два проекта — скрипт, функционал которого описан выше, и базу данных, о которой я так же вкратце упомянул. И условно назовем первый маленьким проектом, а второй — большим.
Особенности маленького проекта:
- Отсутствие мотивации, т.к. не люблю дергаться по мелочам
- Отсутствие бюджета (500 рублей как-то не серьезно)
- Сделал и забыл, никакого расширения в будущем
Особенности большого проекта:
- Постоянное расширение функционала
- Специфичность задачи, как следствие — отсутствие готовых решений
- Приемлемый бюджет
Как все начинается
Думаю, я не ошибусь, что большинство из нас любит интересные задачи. Но как быть, когда тебя просят о помощи знакомые люди? Вроде бы за плечами уже не один серьезный проект, довольные пользователи и несколько лет опыта, но интернет так тесно врос в нашу жизнь, что к нам никогда не перестанут обращаться по мелочам («у моей тёти интернет-магазин кормов для кошек, и ей нужна возможность оплачивать заказ безналом»; «давай сделаем соцсеть, у меня оригинальная идея и через пару месяцев мы будем миллионерами»). Конечно, можно убедить их в том, что они безумны что им это на самом деле не надо, а можно помочь в надежде, что когда-нибудь нам помогут в ответ. Получаем, что и вознаграждение не особо большое, потому что по знакомству, и мотивация одна — сделать быстрее и оставить еще один говносайт, о котором все забудут через пару месяцев, на просторах интернета.
Другое дело когда то, что создаешь, будет реально полезно и функционально (а когда за это хорошо платят то вообще супер). А если еще нужно будет это поддерживать или передавать кому-нибудь, то мотивация писать все четко и структурировано очень сильно возрастает, а постоянное расширение заставляет писать соответствующую документацию, чтоб самому не забыть, какие модули за что отвечают.
Начинаем писать код
Начну с описания большого проекта. Я четко понимал, что работать над ним мне придется одному, данные должны храниться в безопасности, приложение должно получиться расширяемым и легко переносимым с одного сервера на другой или с одной платформы на другую. А еще все должно быть максимально упрощенно для пользователей, поэтому пришлось дополнительно принимать меры по фильтрации и обработке вводимых данных, вести подробные логи, сделать интуитивно понятный интерфейс с расчетом на то, что пользоваться им будет умственноотсталая мартышка после лоботомии.
Дополнительно был настроен сервер mysql с репликацией и мониторинг серверов через zabbix с оповещением на почту mail.ru и последующим смс. Все пишется в стиле MCV, чтоб не запутаться и с легкостью вносить изменения в каждый модуль и чтоб система целиком не рухнула в случае непредвиденного косяка одного из модулей.
Теперь по маленькому проекту. Такого словосочетания, как «техническое задание», человек, попросивший меня приделать к его сайту отправку электронной почты, не знал. Поэтому очень смутно с утра он написал мне, что бы он хотел видеть в итоге и во всем остальном доверился мне. Ок, сделаем как хочешь. Срок — 12 часов.
Прелестный выходной, чудесная погода, а к вечеру 2 новые серии любимых сериалов. В итоге на маленький проект у меня осталось 3 часа.
Казалось бы, времени более чем достаточно, чтобы вставить функцию отправки сообщения, добавления в таблицу в БД, нацепить bootstrap в некое подобие админки и распрощаться с этим клиентом до скончания веков, но врожденное умение находить лишний геморрой на свою голову будет моим вечным поставщиком проблем. Я решил сделать приличную админку с авторизацией, с настройками отправителя сообщения, редактирования тела и темы сообщения пользователю, оставившему заявку, приделать валидацию форм на клиентской и на серверной стороне. С учетом лени и оставшегося времени — странные мысли.
Мешанина VS MCV
Может быть я и не такой крутой кодер, но в среднем каждые полгода я ужасаюсь тому, что написал ранее и понимаю, что можно было сделать это быстрее, проще и безопаснее. Но темная сторона убеждает меня, что это, несмотря на то, что сделано кривовато, работает. Вот к чему это приводит:
Большой проект:
Каждый модуль независим друг от друга. Даже если мы удалим файлы, связанные с пользователями (не из БД, разумеется) система все равно продолжит работать в штатном режиме.
В каждом модуле мы имеем такие файлы:
Controller_name,php
Model_name.php
-templates
action_one.php
action_two.php
…
default.php
Может так и не принято делать, но в данной ситуации это удобно и логично.
В итоге получаем что если пользователь попадет в пункт
/?item=cars&actions=view_last_week
загрузится модуль Cars и подключится шаблон view_last_week.php
Маленький проект:
В начале была вписана такая конструкция:
$action=isset($_GET['action']) ? $_GET['action'] : NULL;
switch ($action) {
case 'read':
$admin->updateStatus($_GET['id']);
break;
case 'delete':
$admin->deleteMessage($_GET['id']);
default:
$admin->view();
break;
}
Теперь что у нас в классе $admin
public function view() {
$messages = $this->getMessages();
?>
<table>
<thead>
<th>id</th>
<th>Номер телефона</th>
<th>Адрес почты</th>
<th>Имя</th>
<th>Статус</th>
</thead>
<tbody>
<?php
foreach ($messages as $message) {
?>
<tr>
<td><?php echo $message['id']; ?></td>
<td><?php echo $message['phone']; ?></td>
<td><?php echo $message['mail']; ?></td>
<td><?php echo $message['name']; ?></td>
<td><?php echo $message['status']; ?></td>
<td><a href="?action=read&id=<?php echo $message['id']; ?>">Прочитано</a></td>
<td><a href="?action=delete&id=<?php echo $message['id']; ?>">Удалить</a></td>
</tr>
<?php
}
?>
</tbody>
</table>
<?php
}
public function getMessages() {
$query = $this->db->prepare('select * from messages');
$query->execute();
$row = $query->fetchAll(PDO::FETCH_ASSOC);
return $row;
}
public function updateStatus($id) {
$query = $this->db->prepare('UPDATE messages SET `status` = ? WHERE id =?');
$query->execute(array('Прочитано', $id));
$this->view();
}
public function deleteMessage($id) {
$query = $this->db->prepare('DELETE FROM messages WHERE id = ?');
$query->execute(array($id));
}
Чем это плохо? Тем, что здесь класс, отвечающий за выборку сообщений и обновление их статуса выводит html код (молчу про отстутствие комментариев и про то, что этот класс отвечает вообще за все в данном контексте). Если бы мне пришлось дорабатывать нечто подобное после кого-нибудь, я мог бы пристрелить этого человека и меня бы оправдали.
А что хорошего? Получается некая имитация динамического удаления или обновления статуса сообщения. Т.е. после нажатия на ссылку «Прочитано» страница обновится и соответствующие данные уже будут в БД, а при удалении отобразится уже новый список. Хотя, конечно, лучше было бы посылать ajax-запрос, который в фоне сделал бы всю работу и убрал бы лишний запрос к БД.
Но зачем беречь ресурсы, которые и так почти не потребляются? Все данные для обновления состояния или удаления сообщений хранятся в одной маленькой таблице и использует их один человек. По поводу расширения -если уж и надумают когда-нибудь приделывать новый функционал, то, скорее всего, просто напишут все с нуля.
Теория баланса
Так что же еще порождает говнокод, кроме незнания возможностей языка и ошибок в методе решения задачи? Элементарная лень и отсутствие мотивации писать правильно. Если вы так же напишите что-то и будете твердо уверены, что этот функционал в неизменном виде будет использоваться пару лет, то когда вам понадобится переписать его, вы глянете, ужаснетесь, и подумаете, что написать все с нуля будет проще и быстрее, чем вспоминать, как все это было реализовано пару лет назад.
Подход к написанию проекта, несомненно, должен определяться в зависимости от поставленной задачи, требуемого функционала, возможности расширения, мер безопасности и конечных пользователей. Так что если ваша реализация позволит все это воплотить, проект будет работать и пользователь будет доволен — возможно отклониться от рекомендуемых шаблонов. Но лучше не делать из этого привычку и писать так, будто поддерживать систему после вас будет псих, который знает где вы живете.
Автор: MetaDone