Форумы портала PHP.SU » Разное » Обсуждение статей » Программирование: теория и практика. Часть 0.

Страниц (1): [1]
 

1. EuGen - 01 Декабря, 2011 - 10:48:32 - перейти к сообщению
Приветствую, коллеги.

Вместо вступления
Написание этой статьи вызвано большим интересом к теме программирования "как оно есть", имея ввиду именно создание программы, отвлеченно от программирования на PHP или других языках. Что же здесь имеется ввиду? То, что программирование - это вполне теоретизируемая область, обладающая рядом постулатов, правил, методов. И именно незнание этой теории, на мой взгляд, приводит к тому, что называется "быдлокодом" (Лично мне не нравится это название, поэтому здесь и в дальнейшем буду называть это просто "нестройный код")

Кратко о теории
Пересказать теорию я, наверное, не сумею, да и заняло бы это слишком много. Теория ведь бывает разная. И я, к примеру, изучал ее в течении нескольких лет в ВУЗе. Курсы, которые преподавались там - это Структуры Данных и Алгоритмы (СДА), Языки Программирования и Методы Трансляции (ЯПМТ), Компьютерная Графика (КГ), Интернет-Технологии (ИТ) и т.п. Как видите, пересказать все это довольно трудоемко. Поэтому расскажу лишь немного о самой теории, но не ее содержание.
Теория позволяет воспринимать процесс абстрактно, посредством сущностей и связей между ними, а так же побуждает к осознанию сущностей, которые Вы используете в конкретной программе. Приведу пример. Простой код, задача которого - сделать замену в тексте так, чтобы все первые буквы были заглавными. Вариант первый:
PHP:
скопировать код в буфер обмена
  1. $t=preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
  2. for($i=0; $i<count($t);$i++)
  3. {
  4.    $t[$i]=ucfirst($t[$i]);
  5. }
  6. $r=join(' ', $t);

Вариант второй:
PHP:
скопировать код в буфер обмена
  1. $sResult=join(' ',array_map('ucfirst', preg_split('/\s+/', $sText, -1, PREG_SPLIT_NO_EMPTY)));

Примеры приведены с тем, чтобы показать наличие сущностей. В первом примере их целых три. Это $t, $i и $r. Да-да, переменные - один из видов сущностей. Ведь создавая переменную, мы закладываем в неё некоторый смысл. И пусть $i используется только в цикле, а $t - временная. Обе имеют смысл, первая - как итератор цикла, вторая - как временное хранилище. $r в первом случае есть сущность-результат.
Теперь придем к понимаю, почему первый пример хуже второго. Хуже - с точки зрения теории программирования. Дело здесь не в том, что второй выглядит "красивее", имея всего лишь одну строчку кода. Если знать, почему так произошло, это будет лишь естественным следствием. А дело в простом. В первом примере у нас есть две лишних сущности. Это $t и $r. Почему лишних? Потому что, очевидно, ни итератор цикла, ни временное хранилище не имеют дальнейшего значения, их роль ограничена лишь этим участком. И, конечно, кроме практической цели у них нет строгого логического обоснования. Попросту говоря, они - просто переменные. Сущностью в первом примере является только $r - так как это - результат (с которым, конечно, предполагается что-то делать). И, скорее всего, данные пришли исходя из определенного алгоритма, а потому $r будет участвовать далее как сущность. Во втором же примере мы обходимся без лишних сущностей (есть только одна - собственно, результат), а потому эти два примера иллюстрируют одно из правил:
0. Избегать лишних сущностей в программе

Помимо сущностей есть еще и связи между ними. А так же более сложные сущности, чем, скажем, переменная. Здесь, конечно же, идет отсылка к ООП. Имею ввиду именно сам объектный подход - не его реализацию в конкретном языке, а именно теорию. Каждый раз я вижу, что, пытаясь постигнуть ООП, начинающие программисты безнадежно увязают в хитросплетениях его конкретной реализации в конкретном языке. А это - не есть ООП, так как ООП - это парадигма, теоретическое построение, если хотите. Поэтому нельзя вникнуть в него просто начав "писать код". Осознание должно быть на теоретическом уровне, абстрактном уровне мышления. Ровно поэтому при описании, обучению ООП часто можно видеть отвлеченные от программирования примеры ("Представим себе, что у нас есть сущность - фрукт, и ее экземпляры - банан, апельсин, груша, и у них есть свойства {...} и т.п."). Ведь если все мы вспомним, как в детстве нас учили числам - мы придем к тому же самому. Все помним, как считали яблоки в раннем детстве? Это может показаться смешным или странным, но именно тогда в нас всех закладывалось абстрактное восприятие чисел. Спросите сейчас себя - что такое "число семнадцать"? Вы не найдете точного ответа, так как это - абстракция, отвлеченно от реального применения (семнадцать яблок, семнадцать мгновений весны и т.п.) не имеющая конкретного смысла. Аналогично и ООП в программировании. Это - абстракция, это - теория. И разница в понимании/непонимании ее - на том же уровне, как понимание/непонимание абстракции чисел. Оттого и сложно ее постичь.

Как помочь в восприятии

Если рассматривать код с теоретической точки зрения, то в хорошем коде сущности обозначают сами себя и множество допустимых связей и отношений с другими сущностями. Можно помочь сделать это обозначение в самом коде. Как правило, неймспейсинг и правила написания призваны это сделать. Иными словами, сейчас мы снова пришли к правилу, которое является следствием теоретического подхода:
1. В хорошем коде сущности выделяют себя сами. И этому помогает выделение в самом коде

- это именно правила "хорошего тона" в программировании, при создании имен сущностей. Хорошее имя переменной скажет вам, зачем она нужна, а хорошее имя функции - что она делает. Как видите, правила хорошего кода тоже возникли не на ровном месте и не только для удобства, а являются следствием теоретического обоснования. Чтобы была конкретика, приведу правила, которых придерживаюсь сам.
0. Неймспейсинг:

  1. Имена переменных. Используются префиксы, указывающие на тип переменной, обозначая таким образом множество допустимых над ней операций. Я использую префиксы:
    rg - для массивов
    b - для булевых данных
    i - для данных целочисленного типа
    f - для данных вещественного типа
    s - для данных строкового типа
    r - для данных типа ресурс или объект.
    m - для данных, тип которых заранее неизвестен (часто при лямбда-функциях, используемых при обработке массивов)
    fn - для callback-данных
  2. Имена функций.
    Используются как префиксы, так и словообразование. Например, функцию, возвращающую MAC-адрес по IP, я назову getMACbyIP. Традиционно функции я именую с маленькой буквы, помещая действие, которое выполняет функция, в начало ее названия и отделяя его строчным написанием.
  3. Имена классов. Всегда с заглавной буквы.
    Класс ассимилирует в себе свою принадлежность. Следствием этого является простота написания маршрутизации, используемой при автозагрузке классов (через __autoload)
  4. Имена методов.
    - все публичные методы объявляются с самого начала и следуют названиям функций.
    - все защищенные (protected) методы начинаются с
    подчеркивания, следуя названиям функций
    - все приватные (private) методы начинаются с
    двойного подчеркивания, следуя названиям функций

1. Объявление сущностей.
Здесь имеется ввиду, что все переменные инициализируются, прежде чем будут использованы.
2. Избегать приемов, затрудняющих чтение имени.
Для PHP это, прежде всего, неочевидный динамический вызов. Нельзя сказать, что он - плох, и в ряде случаев его использование позволяет решить задачу красиво и лаконично, но злоупотреблять переменными-функциями и переменными-методами не стоит.

Кажому - свое
Помимо того, что сущности должны быть четко определены, иметь обозначенный (или обозримый) круг связей и отношений с другими сущностями, они должны еще и нести именно тот набор ролей/функций, которые отвечают их логическому обоснованию.
Иными словами, каждый метод класса должен решать именно ту задачу, для которой создавался, и никак не заботиться о чем-либо еще, никак не брать на себя какие-либо другие функции. Снова рассмотрим пример. Допустим, у нас есть функция, добавляющая пользователя в БД. У пользователя есть логин и пароль. Первый пример:
PHP:
скопировать код в буфер обмена
  1. function newuser_add($login, $password, $link=null)
  2. {
  3.    if(!$login || !$password)
  4.    {
  5.       return false;
  6.    }
  7.    if(!mysql_query('INSERT INTO users (login, password) VALUES ("'.mysql_real_escape_string($login)."', '".mysql_real_escape_string($password).'")', $link))
  8.    {
  9.       exit('Failed!');
  10.    }
  11.    return mysql_insert_id($link);
  12. }

И второй пример:
PHP:
скопировать код в буфер обмена
  1.  
  2. function checkUser($rgUser)
  3. {
  4.    $bResult=true;
  5.    array_walk($rgUser, function($mValue) use (&$bResult)
  6.    {
  7.       $bResult = $bResult && ($mValue!=='');
  8.    });
  9.    return $bResult;
  10. }
  11. function addUser($rgUser, $rLink)
  12. {
  13.    if(!checkUser($rgUser))
  14.    {
  15.       throw new Exception("Couldn't add user due to incompleted data fields");
  16.    }
  17.    if(!mysql_query('INSERT INTO users (login, password) VALUES ("'.mysql_real_escape_string($rgUser['login'])."', '".mysql_real_escape_string($rgUser['password']).'")', $rLink))
  18.    {
  19.       throw new Exception("Couldn't add user due to mysql-returned error: ".mysql_error());
  20.    }
  21.    return mysql_insert_id($rLink);
  22. }

Как мы видим, в первом примере мы во-первых, работаем с сущностью, не соответствующей имени функции (если все правильно - то функция должна работать именно с той сущностью, которая фигурирует в ее названии), а во-вторых, функция берет на себа задачу по фильтрации результата и даже по управлению исполнением всей программы. Во втором же случае проверку данных мы поручили отдельной функции, также работающей с сущностью $rgUser - "пользователь". Кроме того, все исключительные ситуации мы поручаем внешней среде, так как наша цель - только добавить пользователя. Кроме этого, результат всегда определен - это целое число - сущность, указывающая на строку как на результат. Как видно из этого примера, мы получаем следующее правило:
2. Каждая функция/метод должна нести только тот набор ролей и функционала, который логически обоснован её сущностью

То есть каждый метода/функция должен заниматься строго своей работой. Замечу, кстати, что в продемонстрированном примере из теории мы увидели обоснование того, что функция должна возвращать результат одного типа, так как в противном случае смысл результата как сущности теряется.

На этом я завершаю эту часть статьи. Информации довольно много, и если у вас, уважаемые читатели, есть вопросы или критика - буду рад ответить на них. Надеюсь, эта небольшая статья поможет вам прояснить вопросы, которые у вас были до ее прочтения.
2. Мелкий - 01 Декабря, 2011 - 11:07:54 - перейти к сообщению
Замечу, что у функции newuser_add третий параметр NULL равен быть не может, т.к. будет вызывать ошибку mysql_query.
Функцию addUser тоже поправь - переменные $login и $password берутся из неоткуда, передан-то массив Подмигивание

Кстати, давно хотел спросить - почему массив - rg? От чего образован? Остальные-то понятно.
3. EuGen - 01 Декабря, 2011 - 11:09:01 - перейти к сообщению
Да, верно. Как понимаете, пишу без проверки. Исправлено.

rg - это от register

Касаемо newuser_add - так и задумано. Чтобы продемонстрировать, что неверно определенный тип данных может разрушить логику исполнения.
4. DeepVarvar - 01 Декабря, 2011 - 12:12:26 - перейти к сообщению
EuGen пишет:
0. Избегать лишних сущностей в программе
Кажется к этому есть соответствующее очень старое слово "бамминг", т.е. сокращение программы и возможное или даже явное увеличение производительности.

Кстати хотел спросить верно ли я делаю когда слежу даже за кол-вом ссылок на объекты, а именно, если не нужен клон или копия объекта, то и ссылка на "фабрике" одна.
Видел как-то код где в конструктор всех объектов передавлась ссылка на "папу", в итоге на протяжении приложения плодилось 20 и более ссылок на него...
5. EuGen - 01 Декабря, 2011 - 12:16:07 - перейти к сообщению
DeepVarvar пишет:
Кажется к этому есть соответствующее очень старое слово "бамминг"

Задача - не сократить код предельно. Например, в случае с добавлением пользователя в этой статье, напротив, была выделена еще одна сущность, то есть по факту код стал длиннее.
Задача в том, чтобы не существовало лишнего, а то, что есть - не было перегружено ненужными вещами.
Следить за ссылками не вредно никогда, но превращать это в нечто маниакальное тоже не стоит.
6. DeepVarvar - 01 Декабря, 2011 - 12:18:23 - перейти к сообщению
Да, согласен, я не совсем в точку, когда был придуман "бамминг", ООП еще не было в помине.
И действительно, второй пример указывает именно на ООП подход.
(Добавление)
А что скажете насчет ссылок в моем вопросе?
7. EuGen - 01 Декабря, 2011 - 12:21:19 - перейти к сообщению
DeepVarvar пишет:
второй пример указывает именно на ООП подход.

Примерно такой и расчет. Однако, заметьте, там нет ни классов, ни объектов. Суть как раз в том, чтобы реализовывать парадигму ООП, это можно сделать и в таком простом примере.
8. DlTA - 01 Декабря, 2011 - 12:24:39 - перейти к сообщению
EuGen, я не знаю чем ты обромляешь
Спойлер (Отобразить)

можешь так больше не делать, у меня от этого появляется горизонтальная прокрутка, пипец как не удобно
9. EuGen - 01 Декабря, 2011 - 12:44:29 - перейти к сообщению
DlTA
Изменил форматирование, благодарю за замечание.
10. DlTA - 01 Декабря, 2011 - 12:53:46 - перейти к сообщению
спасибо
11. Bio man - 23 Февраля, 2012 - 16:03:27 - перейти к сообщению
как понять
EuGen пишет:
fn - для callback-данных
?
можно на примере показать что такое callback данные?
12. Bio man - 23 Февраля, 2012 - 18:46:26 - перейти к сообщению
up
13. caballero - 23 Февраля, 2012 - 19:26:14 - перейти к сообщению
Цитата:
Используются префиксы, указывающие на тип переменной, обозначая таким образом множество допустимых над ней операций.

"Венгерская" нотация в нетипизированном PHP - однако.

как по мне второй пример хуже. Особенно где проверка юзера.
Во первых не все поля могут быть обязательными
во вторых такая проверка нечитабельна.
Это и есть теория которая противоречит практике.
14. Bio man - 23 Февраля, 2012 - 21:58:35 - перейти к сообщению
так что такое callback данные?
15. EuGen - 23 Февраля, 2012 - 22:30:44 - перейти к сообщению
Bio man
Навскидку:
PHP:
скопировать код в буфер обмена
  1. $fnTrimBrackets=function($sData)
  2. {
  3.    return trim($sData, '()[]{}');
  4. };
  5. //some code..
  6. $rgData=array_map($fnTrimBrackets, $rgData);
  7. //some code..

caballero
Личное дело каждого какой стиль выбрать. Выше - лишь рекомендации. А префиксы как раз и направлены на то, чтобы внести ясность, так как эта самая ясность отсутствует - нет строгой типизации.

 

Powered by ExBB FM 1.0 RC1