RizVN Login



Модульный принцип: несколько моментов

Модульный принцип: несколько моментовВ рамках данного обзора, я расскажу вам несколько моментов о применении модульного принципа. И начну с определения, чтобы напомнить читателям, о чем речь..

Модульный принцип - это принцип, который подразумевает, что программа представляет собой композицию логически связанных между собой подпрограмм или модулей.

Такой подход подразумевает немало плюсов, таких как: возможность распараллеливания задач, унификация, простота тестирования, экономия кода (при частом повторении), легкость переноса кода из одной программы в другую и тому подобное. Но, и минусы существуют, такие как: увеличение времени на создание (за счет необходимости универсальности, может и неполной), потенциальные проблемы с производительностью (многие модули подразумевают массу лишних действий, например, повторные проверки значений переменных и т.п.) и прочее.

Многие из этих моментов достаточно детально описаны в интернете. Поэтому далее рассмотрим несколько аспектов, которые не так часто встречаются.

 

Баланс между размером модуля и его гибкостью

Модульный принцип: несколько моментов

Основная суть проблемы баланса размера модуля и его гибкости в следующем: чем больше функций выполняет модуль, тем сложнее сделать его универсальным или удобным для использования. Причина же: с каждой добавленной функцией растет количество внутренних взаимосвязей.

Утрированный пример. Допустим, существовал класс для подсчета финансовых данных.

<?php
class MyFinReport {
    public function formDataOneReport($some_settings) {
        // как-то данные формируются
    }
}

Решили добавить несколько отчетов и, для упрощения расчетов, вынесли "подготовку данных" в отдельную функцию.

<?php
class MyFinReport {
    protected function _preFormData($some_settings) {
        // как-то данные проверяются и формируются
    }
    public function formDataOneReport($some_settings) {
        $data = $this->_preFormData($some_settings);
        // как-то данные формируются
    }
    public function formDataTwoReport($some_settings) {
        $data = $this->_preFormData($some_settings);
        // как-то данные формируются
    }
    public function formDataThreeReport($some_settings) {
        $data = $this->_preFormData($some_settings);
        // как-то данные формируются
    }
}

Через некоторое время возникла необходимость добавить четвертый отчет, для которого не подходит добавленная функция "_preFormData" для "подготовки" входных данных. В такой ситуации либо внутри функции "_preFormData" возникнет большой if-else, либо только внутри этой добавленной функции (четвертого отчета) будет отдельный расчет, либо будет добавлена еще одна защищенная функция, либо от функции "_preFormData" и вовсе придется отказаться (раскидав обратно код для каждого отчета).

Пример можно и далее продолжать, но даже из вышеперечисленного видно, что чем больше становится функционал внутри класса "MyFinReport", тем больше возникает таких вот нюансов в стиле "НО". Например, если добавить какой-то дополнительный параметр для нескольких отчетов, то в коде возникнет еще больше "подпорок".

Примечание: Безусловно, подобная проблема решается разделением на под классы (под модули). Но, в этом то и "соль", что в рамках одного модуля увеличение функционала может приводить к подобным проблемам.

Этот же момент с балансом касается и обратной ситуации, при которой модули решают слишком простые задачи. Суть в том, что слишком много мелких абстракций это куча кода. Утрированный пример - это создавать классы для выполнения элементарных арифметических операций по типу:

<?php
class Plus {
    public function calc($a, $b) {
       return $a + $b;
    }
}
$modif = new Plus();
$result = $modif->calc(123. 123);

Конечно, бывают ситуации, когда такое может требоваться (например, если используются несуществующие операции и нужно стандартизировать вызов через полиморфизм), но такое бывает редко. Вот пример с полиформизмом:

<?php
//...
class Plus extends Operation
//...
class SpecificOperation extends Operation
//...
foreach($operation_array as $item) {
   $operation = $item['operation'];
   $settings = $item['settings'];
   $result = $operation->calc($result, $settings);
}
//...

Так же момент баланса между размером и гибкостью касается того, что некоторые модули, которые изначально писались как универсальные, из-за недостатка времени начинают включать в себя специфические моменты. Например, внутренние константы, привязку к адресам и тому подобное (как говорится TODO). Это, в принципе, нередкая проблема для больших модулей. Поэтому, тут, как говорится, "лучше если хоть часть будет сделана хорошо" (иными словами, если риск возникновения проблем со временем немалый, создавать пусть и не огромные, но зато стабильные модули).

 

Версии модулей

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

<?php
class MyModule {
    public function A() {}
}

Стал класс

<?php
class MyModule {
    public function A() {}
    public function B() {}
}

Однако, когда меняются входные параметры или же результат какой-либо функции становится иным, то это приводит к проблемам. Например:

<?php
// Было
class MyModule {
     public function A() {
          return 1;
     }
}
// Стало
class MyModule {
     public function A($settings) {
          return array (
              'settings' => $settings,
              'result' => 1
          );
     }
}

Если данный пример кажется тривиальным, то, когда речь заходит о возможностях языка, этот момент может быть неочевидным. Например, вместо типа bool возвращается int в значениях 0 или 1. Казалось бы, ничего страшного. Но, вот если в коде используется проверка с учетом типов (например, в php это оператор "===" - три равно, вместо двух), что может быть незаметно при использовании старой версии модуля (так как использовался bool), то, после изменения типа в int, это может быть проблемой, так как "false" не равно "0" и "true" не равно "1".

Так же важно понимать, что, порой, ограничения модуля обходят за счет использования его "фичей" (не ошибок, а особенностей) или же просто используют их. Допустим, существовал класс логгера:

<?php
class Logger {
    protected $log_message = '';
    public function add($message) {
        $this->log_message .= $message;
    }
    public function getLog() {
        return $this->log_message;
    }
    public function clear() {
        $this->log_message = '';
    }
    public function save($path) {
        file_put_contents($path, $this->log_message, FILE_APPEND | LOCK_EX);
    }
}

Как видно, в данном классе, при сохранении лога в файл, сам лог не очищается. Подобное поведение сложно назвать ошибкой, так как сама операция сохранения это неоднозначное действие. Соответственно, люди могут использовать эту "фичу", для того, чтобы формировать несколько разных файлов с логом. Например, в стиле "первые несколько сообщений содержат краткую информацию и добавляются в один лог, в последующем более технический лог добавляется в другой файл". Соответственно, добавив очистку при сохранении ("по просьбам пользователей, которым надоело создавать объекты или вызывать функцию clear"), может возникнуть проблема, которая не сразу обнаружится (логи же не обязательно каждый день/неделю/месяц смотрят).

 

Обилие модулей и читаемость кода

Основная суть проблемы в том, что обилие модулей может сделать код нечитаемым или сложным в отладке. По поводу данного момента часть людей может сказать "если знаешь свой код, то это не проблема". И в этом, собственно, проблема, особенно если модули нетривиально взаимосвязаны (например, из-за неграмотной архитектуры). Представьте, что возникает необходимость отлаживать свой код 10-летней давности или вообще смотреть чужой код в вышеописанном стиле. И вы видите примерно следующее (утрированный пример):

<?php
//...
$temp_data = $someObj->formData($connector, $auth, $logger);
$converter = new SDConverter($someObj, $connector, $auth, $logger);
$img_rotator = new ImgRotator($settings_rotator, $connector, $auth, $logger);
$img_binary = $converter->serialize($temp_data, $img_rotator);
$cell = new SDCell($settings_cell);
$cell->setImg($img_binary);
//...

Внешне код выглядит вполне нормально. Но, если приглядеться, то возникает масса вопросов: "почему someObj нужен при создании конвертера", "почему если используется someObj при инициализации конвертера, то данные передаются отдельно", "SDConverter только сериализует данные картинки или так же их редактирует? ведь ему на вход передается ротатор, а не его результат" и тому подобное.

Если же приводить пример нечитаемого кода, то вот пример "матрешки":

//...
$writer = new SEWriter($settings);
$conv = new SEDLONFormer($settings_former, $writer);
$seper = new SEDLONHybridStreamer($settings_hs, $conv);
$calc = new SEDLONCalculator($settings_calc, $seper);
$result_former = new SEDLONResultFormer($settings_result_former, $calc);
$return = $result_former->formData();
//...

Подобный код это объявление кучи объектов классов, которые передаются в качестве параметров от одной строчки к другой. При этом, понять что за данные формирует этот кусок можно только из комментов, которых обычно нет, так как: "это же очевидно для данного набора классов".

Примечание: Безусловно, бывают ситуации, когда объекты в качестве параметров передаются потому, что возможно использование разных классов. Но, сам факт такой "матрешки" заставляет задуматься "насколько многоуровневая архитектура используется, сколько времени потребуется для ее изучения и насколько она корректна".

Так же советую ознакомиться с обзорами Что лучше небольшие или сложные программы и Правила написания кода: сложные или простые конструкции использовать.

Понравилась заметка? Тогда время подписываться в социальных сетях и делать репосты!

1 1 1 1 1 1 1 1 1 1 Рейтинг 5.00 (1 Голос)

Добавить комментарий / отзыв

Комментарий - это вежливое и наполненное смыслом сообщение (правила).



* Нажимая на кнопку "Отправить", Вы соглашаетесь с политикой конфиденциальности.
Каталог программ