Комментарии 61
Иногда — не долгую…
Ну как сказать. Несколько месяцев уже прошло как мы стали пилить таким образом один большой проект. Пока что всё очень нравится, чем я и решил поделиться с сообществом. Возможно, мне укажут на проблемы, о которых я не подумал. А возможно — кому-то она даст пищу для размышления.
Есть файловая навигация по проекту, а есть и навигация с помощью ReSharper и других подобных инструментов. Инструмент может сказать, что метод toList() реализован в классе Array из файла Array.php, а надо бы ему указать на файл Array_toList.php. Вот такой есть недостаток.
Файловым способом реализовывал модульные тесты для методов веб-сервиса и веб-приложения. Методов очень много и тестов поэтому тоже было много и на один метод несколько тестовых методов. Чтобы проще было реализовать взаимодействие между методами сделал всё в одном классе, но разнёс по файлам. При разработке было очень удобно и удобно отслеживать покрытие тестами.
Потом когда передал код на поддержку коллегам, коллегам было непривычно. Более привычно — разделить код на отдельные классы, сделать много тестовых классов вместо одного с множеством методов.
реализован в классе
нет классов — нет проблем. Есть языки которые позволяют иметь функции как самостоятельные сущности.
Методов очень много и тестов поэтому тоже было много и на один метод несколько тестовых методов.
Опять же, следует группировать по тест кейсам, по фичам. Если у вас есть объект и вы тестируете его поведение и вам удобнее разделить тестировани поведения на отдельные файлы — ок. Но вы не методы тестируете, вы поведение проверяете.
коллегам было непривычно.
Привычно — не имеет значения. Большинству привычно делать стэйт объектов приватным и сразу лепить геттеры. Но это не значит что это "хорошая привычка".
Т.е. как минимум удобство редактирования.
Далее. Как передаете значения переменных? В классе есть, например, public переменная, которую могут использовать все функции класса. А в вашем случае общую переменную нужно тащить в параметры функции. А если их с десяток?
Как я написал в самом начале статьи: я не отказывался ООП-подходов. Если функционал вырастает из размеров одной простой функции, просто берёшь и создаёшь новый класс в проекте и применяешь все фичи ООП, в которых возникает потребность.
Ну как минимум — куча открытых вкладок вместо одной в которой есть список по функциям.
Т.е. как минимум удобство редактирования.
Если речь идёт о небольших функциях, то нет смысла открывать соседние. Каждая функция несёт в себе законченный независимый функционал, который решает определённую проблему. К примеру, вы можете создать функцию, которая находит координаты HTML-элемента на странице. Если у вас возникла проблема с этой функцией — вы работаете только с ней, с одной вкладкой.
Кстати, я сейчас ковырялся в исходных кодах jQuery. Там довольно часто используется подобный подход оказывается. Один файл — решение одной проблемы. Примеры: раз, два, три, и так далее.
P.S. Я не призываю пользоваться таким подходом.
Вы слышали такую фразу, как "всё гениальное — просто"? Современных программистов хлебом не корми, дай где-нибудь что-нибудь усложнить. Ведь когда сложно — это так ласкает наше ЧСВ, не правда ли? Сразу чувствуешь себя этаким Мозгом, решившим проблему таким Великим способом. Вы уже настолько больны этой заразой, что действительно простые методики разработки воспринимаете как нечто унизительное и недостойное внимания.
У меня другой подход в работе: я стараюсь максимально всё упрощать, насколько это вообще возможно. Чтобы больше времени уделять работе над задачами. Для меня код — это инструмент, а не религия и способ удовлетворения чувства собственной важности мозгодробительными конструкциями.
придерживаюсь того же подхода, но…
как это связано с моим комментарием оО?!
Вы же вроде как намекаете, что я тут глупостями регулярно занимаюсь, как я понимаю. Хотя для меня это не глупости: скорее маленькие кусочки мудрости, к которым приходишь только спустя годы практики.
я просто удивился, что вы заново открыли микросервисы
Если коротко, то архитектурный стиль микросервисов — это подход, при котором единое приложение строится как набор небольших сервисов, каждый из которых работает в собственном процессе и коммуницирует с остальными используя легковесные механизмы, как правило HTTP. Эти сервисы построены вокруг бизнес-потребностей и развертываются независимо с использованием полностью автоматизированной среды.
Микросервисы — современное представление сервис-ориентированной архитектуры (SOA), используемое для создания распределенных программных систем. Как и в SOA, модули в архитектуре микросервисов взаимодействуют по сети друг с другом для выполнения цели.
Что-то я не заметил, чтобы коллега saggid предлагал использовать сетевые протоколы для вызова своих функций, так что сравнение с микросервисами удачно только в части "микро".
Основная идея микросервисов в разделении логики на небольшие самодостаточные части.
В таком случае — берите выше! Коллега saggid заново изобрел декомпозицию, которая является краеугольным камнем в разработке сложных систем. Посыл абсолютно тот же, а глобальность аналогии просто зашкаливающая. Круче может быть только изобрести заново двоичную систему.
Дъявол кроется в деталях, если что.
Плюсы:
1. Некоторая наглядность
Минусы:
1. Сложности с PSR (если речь про пых)
2. Усложняется рефакторинг (недостаточно просто изменить имя метода, надо менять и имя файла)
3. Хранить в комментариях старую версию кода неправославно, для этого есть git и иже с ним
4. Появляется невольное желание создавать методы-гиганты
5. Если не используете namespace, то все методы будут глобальными, что совсем нехорошо.
6. Если используете namespace, то доп. сложности с рефакторингом.
7. Нестандартный подход → дополнительное обучение новых программистов
Если ваш проект небольшой, то вы вряд ли столкнетесь с большой болью от минусов.
Если вы храните в этих методах логику, т.е. это методы, которые должны быть в сервисе или модели, то дополнительно будут и архитектурные минусы (отсутствие состояния, невозможность наследования, невозможность DI, IoC и др.)
Давайте рассмотрим внимательно каждый ваш пункт.
Сложности с PSR (если речь про пых)
Какие сложности? Одна простая функция загрузки функций решает все проблемы.
function include_dir($directory)
{
foreach (glob("{$directory}/*.php") as $filename)
{
require_once $filename;
}
}
// Загрузим все функции нашего проекта
include_dir(__DIR__ . '/functions')
Усложняется рефакторинг (недостаточно просто изменить имя метода, надо менять и имя файла)
А когда дело касается классов, то для вас проблемой не является, что их название тоже надо менять?)
Хранить в комментариях старую версию кода неправославно, для этого есть git и иже с ним
Я и не отрицал плюсы от VCS. Однако, почему бы и не написать в комментариях что-то насчёт вашей функции? Кто-то умрёт от этого? Зато лезть никуда не придётся, при необходимости.
Появляется невольное желание создавать методы-гиганты
Сдерживайте своё невольное желание, это не так уж и сложно, поверьте)
Если не используете namespace, то все методы будут глобальными, что совсем нехорошо.
Если ваш проект не является общедоступной библиотекой, ваша глобальная область видимости принадлежит только вам и позволяет делать вам с нею всё, что вашей душе угодно. Насчёт этого я уже написал внизу.
Если используете namespace, то доп. сложности с рефакторингом.
В данный момент я не вижу смысла для проекта, который не будет устанавливаться другим людям, использовать дополнительные namespace'ы.
Нестандартный подход → дополнительное обучение новых программистов
Подход нестандартный, но есть ли в нём что-то запредельно сложное для понимания?)
Если вы храните в этих методах логику, т.е. это методы, которые должны быть в сервисе или модели, то дополнительно будут и архитектурные минусы (отсутствие состояния, невозможность наследования, невозможность DI, IoC и др.)
Всегда ли так нужно это состояние? Так ли нужно наследование для метода, который явно делает какую-то одну конкретную и ясную вещь в проекте? Я в курсе насчёт всего этого и регулярно использую в работе. Но одно другому не мешает. И DI вполне возможен, да, не через конструктор класса, но у нас что, только такой метод получения зависимостей существует в мире?)
Ну так я с вами не особо согласен по всем пунктам, простите уж меня за то, что у меня сформировалось ещё и своё мнение в противовес к вашему. С моей точки зрения, это ваши комментарии достаточно несостоятельны. Вы описали по большей части различные теоретические недостатки, в то время когда на практике я не нашёл проблем в описанных вами местах.
Мне были интересны действительно важные недостатки, из-за которых проект может работать как-то неправильно из-за подобного подхода. То, что описали вы — ну простите, что не угодил вам. Я их критичными проблемами не считаю.
Вам бы для начала, коллеги, договориться о единой шкале оценки предложенного подхода, а уже затем приводить аргументы в защиту своей точки зрения. Но я бы при таких вводных данных
… для проекта, который не будет устанавливаться другим людям
вообще не дискутировал на тему. В своем pet-проекте каждый волен писать код настолько феерично, насколько он чувствует потребность.
Этими словами я имел ввиду разницу между библиотекой, которая будет устанавливаться тысячам других людей и потенциально может сильно помешать им в их работе, и проектом, который является вашим сайтом на одном из фреймворков, к примеру. Большинство людей пишут всё-таки какие-то проекты, которые никогда не будут доступны общественности, только небольшим группам разработчиков, которые ведут разработку этого проекта и могу договориться друг с другом.
Я не имел ввиду здесь маленькие домашние проекты.
Повторю то, что я писал ранее: PHP весь работает на глобальных функциях. Живём ведь счастливо с этим делом. Всё потому, что эти глобальные функции всегда имеют один набор. Точно также можно сделать свой собственный набор дополнительных глобальных функций для своего собственного проекта, чтобы удобнее работать с ним. И я не вижу здесь проблем в разработке даже достаточно больших проектов. Тот проект, который мы делаем сейчас (образовательный портал с кучей модулей) — его маленьким не назовёшь.
И ещё раз повторю. Я не призываю писать вообще весь проект только на фукнциях. Упаси Господи от таких мыслей. ООП прекрасен, он позволяет делать массу крутых вещей. Но также бывают и множество ситуаций, когда создание класса является излишним, а иногда даже менее удобным, чем создание набора функций и расположение каждой из них в своём файле.
Насчёт остальных языков программирования я не знаю.
Так сделано в LabVIEW — одна функция (точнее, один инструмент) — один файл, и по другому там нельзя (исключая, пожалуй, упакованные библиотеки и старые llb, но о них особый разговор).
Помимо раскладывания по папкам (это можно как по физическим на диске так и по виртуальным в проекте) файлы можно объединять в библиотеки (по сути их будет объединять XML файл со ссылками на индивидуальные файлы — функции).
С классами тоже самое — один метод — один физический файл на диске, объединённые в класс. Ну само собой, методам можно задавать Public/Protected/Private и всё такое.
Ну вот, к примеру, если взять первый попавшийся код примера из учебника:
using System;
namespace BoxApplication
{
class Box
{
private double length; // Length of a box
private double breadth; // Breadth of a box
private double height; // Height of a box
public void setLength( double len )
{
length = len;
}
public void setBreadth( double bre )
{
breadth = bre;
}
public void setHeight( double hei )
{
height = hei;
}
public double getVolume()
{
return length * breadth * height;
}
}
class Boxtester
{
static void Main(string[] args)
{
Box Box1 = new Box(); // Declare Box1 of type Box
Box Box2 = new Box();
double volume;
// Declare Box2 of type Box
// box 1 specification
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 specification
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// volume of box 1
volume = Box1.getVolume();
Console.WriteLine("Volume of Box1 : {0}" ,volume);
// volume of box 2
volume = Box2.getVolume();
Console.WriteLine("Volume of Box2 : {0}", volume);
Console.ReadKey();
}
}
}
то эквивалентный код в LabVIEW и проект будет выглядеть как-то вот так:
![](https://habrastorage.org/files/836/178/498/836178498257472ca92014f7630f1971.png)
на диске оно будет выглядеть вот так:
![](https://habrastorage.org/files/0e8/bb3/6b6/0e8bb36b6b92425b91130c5a16b40f11.png)
Собственно методы там вот какие:
![](https://habrastorage.org/files/94a/2cd/072/94a2cd072362410eb9099e3556823412.png)
Тут есть и плюсы и минусы — если делать классы или библиотеки-монстры и валить все методы и функции в одну папку, то потом разобраться будет непросто.
А изначально хорошее проектирование и грамотное разбиение на модули делает работу и последующую поддержку удобной. У меня довольно большой проект — дискомфорта нет, всё достаточно наглядно и прозрачно.
Ну и еще мы в команде накладываем строгое ограничение на размер кода — в смысле один инструмент-функция — один экран. Если код начинает разрастаться, то бьём это дело на более мелкие, либо организуем подприборы.
P.s. сам ваял программы размером с лист А1
Я достаточно быстро прошёл фазу «программ на А1», теперь я могу практически любую диаграмму быстро привести в компактный вид (впрочем я уже шестнадцать лет на LabVIEW программирую). В принципе ничто не мешает использовать эту среду как язык «общего назначения» — от специфического оборудования сама среда в общем-то не зависит, просто есть удобные библиотеки к железкам от того же производителя, что и LabVIEW. Задачи тоже не обязаны быть специфическими, но исторически так уж сложилось, что в основном LabVIEW используется либо в научной среде, (скажем в гамбургском синхротроне DESY много чего на LabVIEW сделано), либо в промышленности (я работаю в области промышленной автоматизации и машинного зрения в области неразрушающего рентгеновского контроля). Ну и ещё недетская стоимость продукта и библиотек останавливает широкое распространение — там ценник уже не на сотни, а на тысячи евро идёт.
Сегодня эта ссылка указывает точно на метод под названием booleanConditional. А вот на что эта ссылка будет указывать спустя пол года — на это мы посмотрим спустя пол года
Поэтому надо указывать в URL не master, а хеш коммита. В результатах поиска github формирует ссылки с хешем.
https://github.com/systemjs/systemjs/blob/96fefe138ad4d60f46c3bdbb32a43490877209e7/lib/conditionals.js#L126-L147
Теперь найти нужную функцию проекта стало в несколько раз проще, при этом можно использовать почти любой редактор кода: главное чтобы он умел искать по названию файла в проекте
А в чем именно проще? Вместо одного поля надо написать название функции в другом поле.
У ядра есть папка functions, в которой находятся ещё пять папок
arrays
dates
strings
В языке есть средства группирования связанных функций, зачем переносить структуру кода на другой уровень в файловую систему?
Зачем вообще глобальные функции? С ними только лишние проблемы — с автозагрузкой, с засорением пространства имен. Все равно у связанных функций часто бывает одинаковое начало в названии — array_assoc_search, array_empty_keys.
Поэтому надо указывать в URL не master, а хеш коммита
Ну окей, будет у вас ссылка на кусок кода, который был актуален когда-то давно, и сейчас он уже неактуален. О чём я и писал в статье, если вы не заметили.
А в чем именно проще? Вместо одного поля надо написать название функции в другом поле.
Читайте более внимательно. Банально не нужен редактор, поддерживающий анализ исходников проекта.
В языке есть средства группирования связанных функций, зачем переносить структуру кода на другой уровень в файловую систему?
Почему нет? Удобно же. Это позволяет сделать более приятной для изучения архитектуру проекта.
Зачем вообще глобальные функции? С ними только лишние проблемы — с автозагрузкой, с засорением пространства имен. Все равно у связанных функций часто бывает одинаковое начало в названии — array_assoc_search, array_empty_keys.
Весь PHP работает на основе глобальных функций. Как люди живут-то с ним? Вы в одну кашу смешали две разных вещи сейчас. Одно дело писать библиотеку, которую потом будет использовать большое количество людей в своих проектах. И там действительно глобальное пространство видимости засорять запрещено. И совсем другое дело — писать свой проект, глобальное пространство которого принадлежит вам и только вам, и вы вольны делать с ним всё, что пожелаете. У вас больше миллиарда допустимых вариантов для имён ваших функций. Вы никому не испортите жизнь таким подходом, ведь все эти функции будут работать только внутри вашего проекта.
Аналогично, для всех атомов хватило электрона, протона и нейтрона, ну ещё фотон.
Ну и ещё пи-мезоны, для "склейки" нуклонов. И кварки, из которых состоят протоны с нейтронами. И глюоны, для склейки кварков в эти протоны и нейтроны.
Аналогия с физикой не самая удачная, там всё не так просто
Ребята, Open GL ни кому не нужен.
Думаешь в DirectX — лучше? Там каждая новая версия может оказаться нифига не совместимой с кодом написанной для старой версии DirectX. Что мягко сказать — очень неудобно для изучения в свободное от работы время.
PS в Геометрии 5 аксиом, однако, теоремы «зачем-то?» доказывают, а не считают лишними.
Дело в том, что просто на подключение всех файлов тоже тратится время. К примеру, Laravel позволяет компилировать все основные классы приложения в один файл на продакшене для ускорения работы проекта.
Нет, это немного другое) Вы смешиваете разные вещи.
Представим, что в нашем проекте есть 1000 файлов, которые должны загрузиться при каждом запросе. Если вы установили opcache.validate_timestamps
в значение 0
, то PHP не будет проверять скрипты на изменения и будет просто загружать последний доступный скомпилированный код из своего кеша.
При этом, PHP всё также будет обращаться при каждом запросе 1000 раз к файловой системе для того чтобы получить каждый файл. А файловая система будет 1000 раз как минимум проверять наличие этого файла.
Вместо всего этого вы можете объединить вашу тысячу файлов в один большой жирный файл и таким образом сильно сократить количество работы для PHP и вашей файловой системы. Вместе с отключённым параметров opcache.validate_timestamps
, в итоге, вы можете добиться максимальной производительности. Что и можно сделать вместе с Laravel фреймворком прямо из коробки, к примеру.
Действительно, вы правы. Значит это дело реально отпадает, остаётся только то, что я написал ниже насчёт spl_autoload_register
.
К слову, opcache.revalidate_freq=0
делать не надо :)
; How often (in seconds) to check file timestamps for changes to the shared
; memory storage allocation. ("1" means validate once per second, but only
; once per request. "0" means always validate)
; opcache.revalidate_freq=2
Плюс, добавлю, что если вы склеили в один большой файл все свои основные классы, то это приведёт к тому, что ваш код автозагрузки, добавленный в систему с помощью spl_autoload_register
, не будет вызываться для данных основных классов: ведь они уже загрузились в систему, нет смысла вычислять возможные пути их местонахождения и потом пытаться загрузить.
http://stackoverflow.com/questions/21657684/partial-class-in-php-like-we-have-in-c-sharp
В целом это замедление компиляции (.Net), для PHP наверно тоже не очень хорошо с интерпретацией.
Не вижу плюсов на практике. Просматривать историю коммитов приходится редко (и слава богу)
Было лень перечитывать все комментарии, потому может быть будут повторения с тем что выше.
Как это может быть ни удивительно для вас, но проекты можно писать в том числе и используя простые функции, вместо запихивания всего и вся в различные классы.
То есть перефразируя. Вместо того, чтобы функции выносить как статические методы каких-то классов утилит, делаем все обычными чистыми функциями. Это более чем нормальный подход. Вот только повторюсь — только если это чистые функции. Не ну могут быть конечно обертки над какими-то функциями, упрощающие жизнь, но таких функций должно быть минимум.
то на продакшене, по хорошему, надо собирать все функции в один файл и загружать его одного вместо кучи файлов.
В целом в этом нет необходимости. Есть opcache. Еще стоит использовать composer-ские инклуды файлов. И писать ничего не надо.
Там уже давно все адекватные люди компилируют исходники различными готовыми инструментами в один файл.
На клиенте это делают для того, чтобы загрузить все файлы одним HTTP запросом, не более того. Скажем с использованием HTTP2 бандлинг уже не так необходим, но к примеру если мы используем модули и tree-shaking можно существенно снизить размер итогового билда.
Но на сервере, для nodejs, никто таким не занимается. Все тупо грузится в память и в целом количество файлов на этом этапе не влияет на производительность.
Теперь найти нужную функцию проекта стало в несколько раз проще
Все относительно. Если вы скажем новый человек на проекте, вы не знаете имен этих функций. И просматривать сотню файлов по 10 строк становится не так уж и удобно. Да и если вы автор всех этих функций, через полгода вы уже не вспомните названия. Но опять же тут уже дело вкуса.
Тут больше проблема стоит в том чтобы все эти файлы подключить. Вы для этого используете некий include_dir
, но обычные люди захотят чтобы этим занимался composer. А тому проще скормить список файлов.
Инкапсуляция кода и документации
Повторю простую мысль. Ваш подход нормален ровно до тех пор, пока функции остаются чистыми по своей природе. Как только появляются функции вроде send_email_to_user
или event
, теряется информация о зависимостях. То есть вы как бы говорите "у меня шлются ивенты но вам придется искать где и как". Вы так же теряете возможность для применения таких замечательных вещей как open/close principle (расширение функционала без внесения изменений в существующий код), SRP (особенно в случае нотификаций на email, что будете делать если у вас там надо шаблон с красивым email-ом отрендрить?). Ну и я не говорю о том что такие функции нельзя адекватно замокать и как следствие покрыть код юнит тестами.
Ну и я не говорю о том что такие функции нельзя адекватно замокать и как следствие покрыть код юнит тестами.
Почему нельзя адекватно замокать? В целом, механизм сервис-контейнеров Laravel позволяет писать код таким образом, что его можно будет тестировать в самых разных ситуациях. Возможно, эта проблема есть в других фреймворках, конечно. Но здесь я проблем особых и не вижу.
На некоторые вещи ответил вам ниже — промахнулся с нажатием кнопки ответа, видимо..
Повторю простую мысль. Ваш подход нормален ровно до тех пор, пока функции остаются чистыми по своей природе. Как только появляются функции вроде send_email_to_user или event, теряется информация о зависимостях...
Хм, ну, идея эвентов сама по себе является инструментом, который надо использовать с умом. Именно потому что теряется информация о зависимостях, это как всегда тема компромисса между мягкой связанностью компонентов приложения и очевидностью его логики работы.
Вы так же теряете возможность для применения таких замечательных вещей как open/close principle (расширение функционала без внесения изменений в существующий код),
Да, я про это написал в одном из комментов к этой статье.
SRP (особенно в случае нотификаций на email, что будете делать если у вас там надо шаблон с красивым email-ом отрендрить?)
Извините, я не знаю что такое SRP, но красивый шаблон в email сообщении у нас имеется, а код функции send_email_to_user
выглядит вот так:
<?php
use App\Models\EmailTemplate;
use App\Models\User;
use App\Services\Mailing\TemplateCompiler;
function send_email_to_user($template, User $user = null, $data = [])
{
if (is_string($template)) {
$template = EmailTemplate::where('code', $template)->firstOrFail();
}
$templatePath = TemplateCompiler::getViewPath($template->code, $user->settings->language_id);
$data['emailuser'] = $user;
Mail::send($templatePath, $data, function ($m) use ($user, $template) {
$m
->from('projectname', __('projecttitle', [], [ 'language-id' => $user->settings->language_id ]))
->to($user->email, $user->name)
->subject($template->getText($user->settings->language_id)->subject);
});
}
То есть, мы отправляем нужный шаблон нужному пользователю с текстом на том языке, который выбран у него в настройках. В данный момент эта логика работы вполне нормально работает. Да, если будет надо — всегда можно создать ещё один класс-сервис, для отправки писем людям. Это ведь вопрос рефакторинга. Это не так сложно сделать, когда код проекта достаточно опрятен.
Как насчёт класть каждую функцию в свой файл?