четверг, 16 апреля 2015 г.

XC4: что должно быть в config.php/init.php модуля ?

что должно быть в config.php/init.php модуля ?

отвечу, что не должно быть

Не должно быть действий которые зависят от других файлов.
config.php должен быть полностью независимым в том числе и от себя самого и от своего собственного func.php

Вообще в init.php config.php незачем пихать вобще какие-то глобальные переменные/ смарти переменные/ вызовы функций

в большинстве случаев эти вызовы нужны только в локальных конкретных местах, а не для всего магаза

к примеру когда работает image.php или ajax get_block.php он может проинициализировать все активные модули, хотя это реально не нужно.

Отлично если от init.php модуль вообще отказался.

Что влечет несоблюдение этих правил ?

1 тормоза

2 fatal errors

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

4 Проблемы из-за последовательности инициализации модулей


примеры

1) Так нельзя это Fatal error
/*
Load module functions
*/
if (!empty($include_func))
require_once $_module_dir . XC_DS . 'func.php';

if (func_xpc_check_iframe_methods()) {
$smarty->assign('xpc_iframe_methods', 'Y');
}

?>
"modules/XPayments_Connector/config.php" 61L, 3167C




2) Так нельзя это Fatal error и тормоза
.......
x_session_register('cart');

if (!defined('IS_ERROR_MESSAGE'))
include $xcart_dir.'/modules/Google_Checkout/gcheckout_button.php';

"modules/Google_Checkout/init.php" line 77 of 149 --51%-- col 1



3) Это fatal error

if (defined('AREA_TYPE') && in_array(constant('AREA_TYPE'), array('A', 'P'))) {
// Process changes on the module options page
if (isset($_GET['option']) && $_GET['option'] == 'Sitemap') {

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['sitemap'])) {

if (!isset($_POST['sitemap'])) {
$sitemap_error = func_get_langvar_by_name('lbl_permission_denied');
} else {
switch ($_POST['sitemap']['config']) {
case 'add':
$sitemap_error = sitemap_extra_addurl($_POST['sitemap']['add']);
break;

case 'delete':
$sitemap_error = sitemap_extra_delurls($_POST['sitemap']['delete']);
break;

case 'update':
$sitemap_error = sitemap_extra_updateurls($_POST['sitemap']['update']);
break;

case 'generate_cache':
$sitemap_error = sitemap_start_generate_cache($config['sitemap']['cache_limit_general'], $config['sitemap']['cache_limit_categories']);

default:
break;
"modules/Sitemap/init.php"

XC4: Тестирование, выполняемое разработчиками.Чистые тесты.Эволюция ПП

Тестирование, выполняемое разработчиками

Разработчики обычно выполняют "чистые тесты" Разработчики склонны тестировать код на предмет того, работает ли он (чистые тесты), а не пытаться нарушить его работу всевозможными способами (грязные тесты). При незрелом процессе тестирования обычно выполняют около пяти чистых тестов на каждый грязный. В организациях со зрелым процессом тестирования на каждый чистый тест обычно приходятся пять грязных. Это отношение изменяется на противоположное не за счет снижения числа чистых тестов, а за счет создания в 25 раз большего числа грязных тестов ([Boris Beizer]).

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

Переключаться между двумя ипостасями хороший_тестировщик - хороший_разработчик сложно, но необходимо.

В X-Cart нет полноценного масштабного тестирования, поэтому основная надежда именно на тестирование, проводимое разработчиком.



Введена рекомендация
*Для каждой контрибуции указывается число чистых и грязных тестов.Рекомендуется кратко описывать все найденные и исправленные ошибки/проектные решения/исключения/предположения.


Соотношение грязь/чистота позволяет быть честным по отношению к себе.И быть средством самоконтроля.

Если ты проверил успешную регистрацию, проверил успешный checkout, создание ордера
ты сделал 3 чистых теста, но 0 грязных.

Для данного примера соотношение должно быть
3/15 или 3/12

Соотношение 3/0 показывает показывает, что тестирование проведено некачественно.
Мы только нашли узкую тропинку, по которой контрибуция работает,но не нашли огромное кол-во тропинок, когда контрибуция "заблудится"/не работает.
Мы не очертили границы контрибуции, мы не знаем, что она может делать, а что нет.
Значит контрибуция не работает.

Соотношения хуже 1:5-1:4 говорят, что контрибуция некачественная.

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


Одно из важнейших правил эволюционного развития ПП
Одно из важнейших правил эволюционного развития ПП
Главное ничего не сломать, а потом можно уже что-то менять/добавлять.


Если грязных тестов нет, то значит это правило игнорируется разработчиком.

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


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


-Сам модуль
Тут просто тестирование самого модуля, согласно спецификации.

XC4: Тестирование кода/модульность/читабельность/запахи кода

Контрибьютерам/приемщикам в XC

Тестирование кода/модульность/читабельность/запахи кода


подходи к любому изменению - как к источнику багов

если ты не нашел багов - значит плохо поработал
баги есть всегда
весь вопрос в их серьезности



если изменений слишком много
то очень высока вероятность ошибок


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

гораздо лучше когда ханков мало
либо они однотипные
И их правильность доказывается не тестированием, а чтением кода.

требуй хорошей читабельности и модульности - это гарантия что ты найдешь все баги


если читабельность/модульность плохая,
то переделывай


плохая читабельность/модульность это всегда баг, если не сейчас, то при следующем изменении


к примеру если поменялась глобальная переменная variants
то нужно протестировать (а лучше доказать безбаговость чтением кода) всех фишек, завязанных на переменную variants
А лучший способ может быть избавиться от переменной


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

Правильно найти и устранить корневую причину - кучу дерьма
нежели фиксить сам запах.

Если не фиксить кучу, то рано или поздно (или прямо сейчас) она начнет порождать новые запахи - баги.

Без решения родительской-корневой причины фиксить дочерние причины методом заплаток - это неэффективная работа программиста.

XC4: Оформление точки входа модуля.Глобальные переменные

правильно думаю так
if (
active_module_XXX
&& func_XXX_is_allowed_something(common_vars)
) {
func_XXX_do_something1(common_vars);
}

где
func_XXX_do_something существующая функция, которая зависит от переменных ядра



Вопрос1
а как лучше передавать глобальные переменные? Если, например, мне надо записать результат в $smarty. Вызывать ее в функции как global или передавать через переменную по ссылке? и $xcart_dir - ее то наверно можно вызывать как global?

Ответ1
те глобальные переменные которые уже есть
можно использовать как глобальные

новых заводить нельзя

ссылки также ухудшают стабильность и сопровождаемость кода
тк облегачают написание кода, но усложняют его чтение и понимание
должно быть наоборот


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

Комментарий1
> Если, например, мне надо записать результат в $smarty
Считаю, что как объект (который в РНР передается по ссылке)

Вдруг, рано или поздно, у нас будет несколько объектов Smarty, например,
class MailTemplater extens Smarty
и
class HTMLTemplater extens Smarty
- один для подготовки mail body, другой - для отображения в браузер.
В этом случае не придется модифицировать функцию, в зависимости от назначения твоей функции, приедется вызывать её либо дважды, либо единожды:

func_by_chemisk( $smarty, $params);
func_by_chemisk( $mail_smarty, $params);

Комментарий2
А вообще, мне не нравятся многоэтажные условия в if()
Мне не нравится, что код загроможден большим количеством однотипных конструкций вида
if (!empty($active_modules['Some_Module']))
some_function( $arg1, $arg2, $arg3);

Предлагаю вот такое решение чтобы убрать лишний код.
function func_if_module( $module, $function) { // {{{
global $active_modules;
assert( 'isset( $active_modules) && is_array( $active_modules)');
assert( func_num_args()." >= 2");
$arg_list = func_get_args();
$module = array_shift( $arg_list);
$function = array_shift( $arg_list);
if( !empty( $active_modules[ $module]) && $active_modules[ $module] == "Y") {
assert( 'function_exists( $function)');
return call_user_func_array( $function, $arg_list);
}
} // }}}

Комментарий3
когда ты пишешь в коде
func_if_module( "Some_Module", "some_function", $arg1, $arg2, $arg3);

ты обрекаешь пользователя на необходимость зайти в диспетчер, а потом уже и в саму функцию some_function()

если читатель находится на уровне абстракции 1
ты вынуждаешь его спустится на уровень абстракции 2
тк вызов выглядит безусловным

в этом смысле код

if (
active_module_XXX
&& func_XXX_is_allowed_something(common_vars)
) {
func_XXX_do_something1(common_vars);
}

не меняет уровень абстракции


вызовы типа
if (!empty($active_modules['Some_Module']))
some_function( $arg1, $arg2, $arg3);
можно и переделать на
func_if_module( "Some_Module", "some_function", $arg1, $arg2, $arg3);


если вариант
if (
active_module_XXX
&& func_XXX_is_allowed_something(common_vars)
) {
func_XXX_do_something1(common_vars);
}

не будет читаться лучше

Комментарий4
func_if_module - интересная конструкция, вот только проблема: аргументы будут вычислены безусловно, то есть всегда.

да это точно
func_if_module может увеличивать кол-во нотисов в магазине

XC4: контрибьютерам X-Cart насчет безопасности

*подозрительно относиться к операциям обратным к операциям экранирования из prepare.php
stripslahes / func_unhtml / unhtmlentities / unhtml_entitity

*подозрительно относиться к переменным которые проходят двойную конвертацию
например urldecode может выдать SQL-инъекцию даже после прохождения prepare.php

*подозрительно относиться к trusted переменным

*лучше использовать ограниченные белые списки допустимых значений для переменной
пример
$orderby = (!in_array($orderby, array('login', 'name', 'usertype'))) ? 'login' : $orderby;

*не забывать делать escape в шаблонах

*максимально жестко ограничивать всю логику, которая расширяет права пользователя. Или вообще лучше не изменять переменные которые расширят права типа current_area / login/ logged_userid / login_type / identifiers

*делать проверки не хуже чем из login.php при альтернативных логинах. Или лучше полностью задействовать логику из login.php
аналогично для register.php

*не забывать про подключение
include/admin_security.php
include/security.php

XC4: функции и смерть внутри функции (func_header_location/exit/die)

функции и смерть внутри функции (func_header_location/exit/die)



смерть внутри функции противоречит принципам структурного программирования
когда функция всегда должна возвращать управление вызывающей программе
а не прерывать ее выполнение

такие моменты плохо читаются и сопровождаются


к сожалению универсального подхода нет,

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

например из функции с суффиксом ...._redirect() можно и сделать вызов
func_header_location если это вся цель функции это подготовка к редиректу

XC4: инструмент для запуска tplк-модуля без статической врезки

есть инструмент для запуска tplк-модуля без
статической врезки

он не очень хороший
может мы его может поменяем
но он пока такой


[17:50][aim@xcart:p6][~/www/xcart_4_5_x]$ g template_main *
include/func/func.core.php: // $template_main['sitemap_customer'] = 'modules/Sitemap/customer.tpl';
include/func/func.core.php: global $template_main;
include/func/func.core.php: if (!empty($template_main) && is_array($template_main)) {
include/func/func.core.php: $templater->assign('template_main', $template_main);
modules/Products_Map/config.php: $template_main['pmap_customer'] = 'modules/Products_Map/customer.tpl';
modules/Sitemap/config.php: $template_main['sitemap_customer'] = 'modules/Sitemap/customer.tpl';
skin/common_files/common_templates.tpl:{elseif $template_main.$main ne ""}
skin/common_files/common_templates.tpl:{include file=$template_main.$main}

XC4: как define('DEVELOPMENT_MODE', 1) и соответственно MYSQL strict mode помог найти сложную багу

благодаря
define('DEVELOPMENT_MODE', 1);
и соответственно MYSQL strict mode

поймал и обезвредил опасного преступника-рецидивиста в коде
он аффектил логику
Amazon_Checkout
Google_Checkout
3dsecure
и все PG


возникла ошибка

[05-Jun-2012 15:36:54] (shop: 05-Jun-2012 15:34:55) SQL error:
Site : https://xcart2-532.crtdev.local/~aim/xcart_4_5_x
Remote IP : 192.168.10.204
Logged as : demo-customer@x-cart.com
SQL query : INSERT INTO xcart_order_status_history (`orderid`, `userid`, `date_time`, `details`) VALUES ('1038', '', '1338896095', 'a:4:{s:10:\"old_status\";s:1:\"I\";s:10:\"new_status\";s:1:\"F\";s:4:\"type\";s:1:\"X\";s:9:\"reference\";i:7;}')
Error code : 1366
Description : Incorrect integer value: '' for column 'userid' at row 1
Request URI: /~aim/xcart_4_5_x/payment/ps_paypal_advanced.php
Backtrace:
include/func/func.db.php:307
include/func/func.db.php:207
include/func/func.db.php:682
modules/Advanced_Order_Management/func.php:139
include/func/func.order.php:1831
payment/payment_ccmid.php:577
payment/payment_ccend.php:48
payment/ps_paypal_advanced.php:99


стал выяснять

оказалось вот что

1) стартует сессия в payment/auth.php
регаются переменные
в том числе
x_session_register('logged_userid', 0);


2) вызывается
x_session_id
вся сессия сносится
нужно по новому делать вызовы
x_session_register('logged_userid', 0);


3)но это обычно забывают
и получается что
$logged_userid = null


хотя в сессии сидит нормальное значение



те все вызовы
x_session_register

после
x_session_id
становились невалидными

Другой пример
0122437: PayPal Advanced 451 #contrib. нотисы показали что cc_pp3_data не обновляется


[05-Jun-2012 18:15:22] Notice: Undefined variable: skey in payment/prepare_payment_ccwebset.php on line 81
REQUEST_URI: /~aim/xcart_4_5_x/payment/ps_paypal_advanced.php
Backtrace:
payment/prepare_payment_ccwebset.php:81
payment/payment_ccmid.php:575
payment/payment_ccend.php:48
payment/ps_paypal_advanced.php:99


Выводы
*те нотисы это хороший эвристический прием и во многих случаях если думать глубже чем тупо @ или intval() позволяют отловить более серьезную логическую ошибку

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


Минусы 'DEVELOPMENT_MODE'
*проблема это то, что нотисы немного вырывают из текущего контекста если их много

*еще проблема в том, чтобы дойти уровня когда нотисы помогают
нужно долго и долго чистится перед этим
мы 3-4 релиза чистились пока выполнение не стало более-менее чистым