Функции или классы — мысли в слух
Раньше я думал, что программирую плохо, потому-что мне не хватает знаний о теории программирования. Теперь, когда в башке сидит куча теоретической информации — типа «принцип открытия закрытия«, «правило подстановки Барбары Лискоу«, «принцип инверсии зависимостей» и т.д. Я понимаю, что программирую плохо, потому-что слишком много думаю о том, что пишу. Но продолжая испытывать неудовлетворенность от написанного кода, я пытаюсь найти пути его улучшения. Сегодня задумался о том, почему на каждый «чих» нужно писать класс, чем функции хуже?
Почему класс, а не функция?
Итак, первый вопрос — на кой черт нам вообще нужны классы, если есть возможность использования функции? Простого и очевидного ответа на этот вопрос я не знаю. Поэтому, попробую поразмышлять на примере.
Допустим у меня есть задача — объединить содержимое всех текстовых файлов из одной директории. Решить данную задачу можно одной функцией, что-то типа:
function concat($dir) {
//... здесь обходим директорию и возвращаем содержимое всех найденных файлов.
}
Я искренне уверен, что в самой функции ничего плохого нет. Нет, ну правда, функция получилась вполне таки конкретной и реализация не должна вызвать никаких проблем. Но ведь функции не живут сами по себе, у них всегда есть окружение. Поэтому, представим ситуацию, когда данная функция может пригодиться.
Например, ситуация: хочется объединить все javascript файлы в один, для того, чтобы клиент (браузер) мог получать все скопом, а не по отдельности. На Кохане (Kohana) это может быть следующий action:
class Controller_Loader extends Controller {
public function action_all_javascript()
{
echo concat('./js/');
}
}
И опять не вижу в этом, ничего плохого! Кроме одного «но» — как правило, нужно загружать файлы не в произвольном порядке, а во вполне определенном. Для этого проставляю в начале каждого файла номер, соответствующий порядку его загрузки, например: «1core.js«, «2app.js» и т.д. Для того, чтобы не менять контроллер правлю функцию таким образом, чтобы она перед объединением файлов отсортировала их в нужном порядке.
С этого момента можно твердо сказать — код начал загнивать. Т.е. пока не было требования сортировки файлов, вполне можно было обойтись функцией. Теперь же, когда функция решает аж три задачи — сканирование директории, сортировка файлов в нужном порядке и объединение полученных файлов. Код получается невероятно хрупким. В результате, каждое новое требование будет падать на плечи неподъемным грузом.
А что если сделать несколько функций?
Раз новые требования вызывают сложности, то почему не сделать несколько функций вместо одной? Ведь до сих пор не понятно нужен ли класс в данной ситуации!
Ввожу дополнительное требование — порядок объединения файлов касается не только файлов, но и директорий, т.е. необходимо объединить файлы в текущей директорий, затем найти поддиректорию у которой наименьший порядковый номер и объединить файлы в ней и так рекурсивно обойти все дерево. Логика сложна даже для описания, а для реализации подавно.
Одной функцией здесь уже не обойтись, поэтому делаю блюдо из трех функций:
class Controller_Loader extends Controller {
public function action_all_javascript()
{
$fileList = scan_tree('./js/');
$sortedFileList = sort_files_tree($filesList);
echo concat($sortredFileList);
}
}
Преимущества:
Во-первых, в параметрах функций используются простые типы — строка и массив, а это значит что функции получились абсолютно не связанными;
Во-вторых, можно вводить новые сортировки и изменять порядок сканирования, используя указатели на функции.
Недостатки:
Во-первых, появился некий контекст — список файлов и содержимое фалов (явный намек на создание класса);
Во-вторых, логика функции sort_files_tree — невероятно сложная, а значит ее придется разбивать на отдельные подзадачи, что равносильно созданию дополнительных функций (уже появляется некоторая связанность);
В-третьих, добавление новых требований (например: обработка ошибок, объединение файлов определенного типа, разный порядок сортировки и т.д.) приведет к увеличению количества функций и, в конечном итоге, к дублированию кода;
Вывод
Очевидно, что дальше идея использовать функции вместо классов будет приносить все больше проблем, и меньше пользы. Хотя, повторюсь, в первоначальной задаче не было и намека на последующие дополнительные условия, поэтому использование функций казалось вполне оправданным.
Вывод из всего сказанного простой — функции не плохое решения для простых задач, для сложных предпочтительнее строить классы. Остается определить какие задачи простые, а какие сложные.
Мне кажется, что, для всех простых задач в PHP уже есть готовые функции, а если подходящей функции нет, то это говорит о том, что задача не такая уж и простая.