Инструменты пользователя

Инструменты сайта


Боковая панель

Разделы

Общее описание
История изменений
Рекомендации к обновлению
Планы на будущее
Известные проблемы
Онлайн демо
Случайная статья
Видео
Помощь проекту
Люди

FAQ



Редактировать сайдбар

codingguidelines

Ubilling Coding Guidelines & Best Practices или библия Ubilling

Зачем это?

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

  • Понятным
  • Однообразным
  • Полезным
  • Красивым
  • Очевидным
  • Работающим
  • Не ломающим легаси

Кратко будем называть эти критерии П.О.П.К.О.Р.Н.. Если такой набор правил кажется вам слишком сложным, вы можете пытаться следовать упрощенному набору критериев, которым должен соответствовать ваш код:

  • Оригинальный
  • Хороший
  • Удобоваримый
  • Единообразный
  • Не запутанный
  • Эргономичный
  • Не деструктивный

Для краткости будем называть его О.Х.У.Е.Н.Э.Н.

Error reporting

Перед тем, как начать что-то писать, убедитесь что писать вы будете заведомо не в слепую и что-то осмысленное, что будет иметь шанс работать у кого-то еще кроме вас.

Для начала запоминаем, что у большинства пользователей php.ini выглядит следующим образом:

php.ini
post_max_size = 64M
upload_max_filesize = 64M
 
display_errors = On
display_startup_errors = On
log_errors = On
log_errors_max_len = 1024
report_memleaks = On
track_errors = On
html_errors = On
memory_limit = 256M

Соответственно у вас он должен выглядеть аналогичным образом. Если вы не видите кучи warning & notice это не означает что их нету. Их действительно не должно возникать в типичных юзкейсах ни в каких ситуациях. Относительно memory_limit, для новых установок он по-умолчанию 512M, для старых, до 2018 года - 256M. Если ваш модуль и реализовываемые вами механики требуют значительно больше памяти на базах от 10k абонентов - стоит прямо упомянуть требование откручивать для них memory_limit в документации, а также конкретные предпосылки для этого.

Также ваш код должен работать с error_reporting(E_ALL). Если по каким-то причинам он требует error_reporting(0) - значит с вами и вашим кодом что-то не так, можете его спрятать и никому не показывать. Пускай умрет в одиночестве.

Оформление кода

Кодировка символов

PHP-код должен быть представлен только в кодировке UTF-8 без BOM-байта.

PHP-теги

PHP-код обязательно следует заключать в полную версию (<?php ?>) тегов или укороченную (сокращённую запись echo) версию (<?= ?>) тегов и недопустимо заключать ни в какие иные разновидности тегов типа (типа <%).

Базовые конструкции языка и форматирование

Особых требований нет. Рекомендуемой расстановкой фигурных скобок в условиях if, циклах, объявлениях функций и методов являются в «одну строчку», то-есть такие:

protected function listUsers() {
  if ($userCount >= 0) {
     ...
   } else {
   ... 
   }
}

Возможно использование сокращенного формата if в случаях когда это достаточно очевидно и реально экономит место, например

$enableFlag = (wf_CheckGet(array('enablesomething'))) ? true : false;

Относительно сокращенных AND (&&) OR (||) все остается на вашей совести, но в целом экономия одного байта в случае AND и экономия нуля байт в случае OR может не стоить ухудшения читабельности и самоочевидности вашего кода.

На тему табов, отступов и прочего - большинство разработчиков использует IDE NetBeans и рефлекторно нажимает Alt+Shift+F для автоматического форматирования кода.

Именование переменных и констант

Постарайтесь, чтобы объявленные вами переменные своим видом показывали что они такое и зачем они нужны, а в идеале символизировали что в внутри них должно находиться. Допустимыми являются lowerCamelCase и snake_case. Предпочитаемым - lowerCamelCase. Постарайтесь также избежать ubogogo_translita, escheBoleeUbogogoTranslita и стиля «ЧмАфКи ФсЕм КтО в эТоМ чАти». Для констант можете использовать КАПС либо ЗМЕИНЫЙ_КАПС.

Примеры хорошего именования переменных:

$allCities = array(); // очевидно что тут должен быть массив с городами
$allUserData = zb_UserGetAllDataCache(); // ухты, это массив с кэшированными данными пользователей
$totalUsers = 666; // очень похоже на счетчик каких-то пользователей, правда?
$switchesCount = 12; // счетчик свитчей, попробуй не догадаться
$result = 'some string here'; // да, понятно что это результат чего-то что потом будет куда-то возвращено.

И вот в каком виде категорически не хочется видеть работу с переменными и их именованием:

$a=$b+$c;
$huitka=132/4;
$LOLIMHERE=SOMESHIT666();
$f_ar_type_data=12;
$obj1=$obj2;

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

$i = 0; // стандартный инкрементный или декрементный счетчик
$total = 0; // общий счетчик чего-то что мы считали ранее
$count = 0; // приблизительно то же самое по духу
$all = array(); // массив с каким-то набором данных который мы будем потом перебирать
// Конструкции вида $io => $each являются для нас сверхтипичными на проходах массивов
foreach ($all as $io => $each) {
    if (!empty($each)) {
        // $ia, $ib.. etc для вложенных переборов.
        foreach ($each as $ia => $some) {
            $total++;
            $count++;
        }
    }
 
    $i++;
}

Именование классов и объектов

Все довольно просто. Рекомендованным является UpperCamelCase/StudlyCaps для имен классов и общим для переменных lowerCamelCase либо односложный lowercase именованием объектов.

Пример того, как это может выглядеть:

class SexyUsers {
 
    public function __construct() {
        $this->loadData();
    }
 
    public function renderList() {
        .....
    }
 
    protected function loadData() {
        .....
    }
 
    protected function save() {
        .....
    }
 
}
 
$sexyUsers = new SexyUsers(); //нормальный объект 
show_window(__('Some sexy users here'), $sexyUsers->renderList());
 
$sexy=new SexyUsers(); // односложный лаверкейс если и так понятно что это
...
}

Именование методов и функций

Правила также просты и очевидны. В старом функциональном коде использовались функции с префиксом zb_ и Upper/Lower CamelCase именованием вида zb_[Сущность][Что делает?][С чем?]

Пример:

// да, относиться к работе с "адресами", и делает "сменить" чего? Правильно - "имени города"
function zb_AddressChangeCityName($cityid, $cityname) {
...

Либо с префиксом web_ для функций предназначенных для рендера результатов чего-то.

Пример:

// да, возвращает селектор улиц для использования в формочках
function web_StreetSelector($cityid) {
...

Также стоит воздержаться от именования функций именами, слишком сильно напоминающим стандартные функции PHP.

Пример того, как не стоит делать:

function file_put_content($data) {
    ...
}
 
 file_put_content('123');

Предпочитаемыми для именования функций, являются camelCase или snake_case в крайнем случае. Также желательно, чтобы увидев имя функции в авто-комплите IDE можно было бы понять, «что это?», «зачем оно существует?», «что оно делает?», «какие у него параметры?» и «как оно влияет на окружающую среду?».

Пример плохого именования функции нарушающего почти все вышеуказанные критерии:

function zaebisfunkciyamadebyvasya($x) {
    ...
}

А с методами внутри классов что? Предпочитаемым именованием является lowerCamelCase либо односложный lowercase. Как это выглядит можно посмотреть выше, на примере объявления class SexyUsers. А именно как-то так:

protected function loadData() {
        .....
}

Область видимости (protected/public/private) должна быть явно указана для каждого метода. Тоже самое относится и к именованию свойств внутри класса.

Именование аргументов методов и функций

см. «Именование переменных».

Именование опций в конфигах

Просто загляните в файлы конфигурации типа alter.ini и осознайте, что все просто. Имена опций регистро-зависимые, потому КАПСОМ, возможно ЗМЕИНЫМ_КАПСОМ или СЛИТНЫМКАПСОМ. Для булевых значений типа «вкл/выкл» рекомендуется использовать 1/0 соответственно. Строчные значения берутся в кавычки.

Пример того, как это должно выглядеть:

;This option enables sexy users support
SEXY_USERS_ENABLED=1
;sexy users configuration 
SEXY_OPTION="some string"

Сохранение Legacy

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

По поводу сохранения легаси на уровне кода: на данный момент, мы вынуждены и поддерживаем работоспособность Ubilling от PHP 5.3 до PHP 7.4 (да, это возможно и не сложно), из за большого количества старых установок, которые менять никто не будет, так как они успешно работают годами. Поэтому воздержитесь как от использования нового синтаксиса массивов с квадратными брекетами в виде

$userStates = [
    'somelogin' => 'ok',
    'anotherlogin' => 'fail',
];

И используйте старый и в общем-то удобный формат объявления массивов в виде

$userStates = array(
    'somelogin' => 'ok',
    'anotherlogin' => 'fail'
);

Нет, это не относиться к использованию автоинкрементных квадратных скобок в массивах, оно работает нормально:

$data[] = $userLink;
$data[] = $userAddress;
$data[] = $userCash;

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

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

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

Комментирование кода

Комментирование функций и методов - строго обязательное. Комментирование переменных и констант в классах - рекомендуемое. Также если вам не удалось избежать не очевидных мест в коде, комментирование этих мест также рекомендуется. Формат комментариев, PHPDoc совместимый для методов и функций, либо однострочный для маленьких комментариев внутри кода. Язык комментариев - базовый английский. Шекспир отдыхает. В крайнем случае воспользуйтесь Google translate. Выглядеть это должно следующим образом:

class UbillingFuneralServices {
 
    /**
     * Contains system alter config as key=>value
     *
     * @var array
     */
    protected $altCfg = array();
 
    /**
     * Contains graves as id=>grave data
     *
     * @var array
     */
    protected $allGraves = array();
 
    /**
     * System message helper placeholder
     *
     * @var object
     */
    protected $messages = '';
 
    /**
     * Default module URL
     */
    const URL_ME = '?module=funeral';
 
    /**
     * Creates new funeral services instance
     * 
     * @return void
     */
    public function __construct() {
        $this->loadConfig();
        $this->initMessages();
    }
 
    /**
     * Loads required system configs into protected properties for further usage
     * 
     * @global object $ubillingConfig
     * 
     * @return void
     */
    protected function loadConfig() {
        global $ubillingConfig;
        $this->altCfg = $ubillingConfig->getAlter();
    }
 
    /**
     * Inits system messages object
     * 
     * @return void
     */
    protected function initMessages() {
        $this->messages = new UbillingMessageHelper();
    }
 
    /**
     * Returns current graves data from protected property
     * 
     * @return array
     */
    public function getGraves() {
        $result = $this->allGraves;
        return ($result);
    }
 
    /**
     * Sets some grave state 
     * 
     * @param int $graveId
     * @param array $graveData
     * 
     * @retrun void
     */
    public function setGraves($graveId, $graveData) {
        if ((isset($graveData['login'])) AND ( isset($graveData['depth'])) AND ( isset($graveData['price']))) {
            $this->allGraves[$graveId] = $graveData;
        }
    }
 
    /**
     * Renders existing graves list 
     * 
     * @return string
     */
    public function renderGravesList() {
        $result = '';
        if (!empty($this->allGraves)) {
            $cells = wf_TableCell(__('ID'));
            $cells.= wf_TableCell(__('User'));
            $cells.= wf_TableCell(__('The depth of the grave'));
            $cells.= wf_TableCell(__('Price'));
            $rows = wf_TableRow($cells, 'row1');
            foreach ($this->allGraves as $io => $each) {
                //single string comment here
                $cells = wf_TableCell($io);
                $cells.= wf_TableCell($each['login']);
                $cells.= wf_TableCell($each['depth']);
                $cells.= wf_TableCell($each['price']);
                $rows.= wf_TableRow($cells, 'row3');
            }
            $result.=wf_TableBody($rows, '100%', 0, 'sortable');
        } else {
            $result.=$this->messages->getStyledMessage(__('Nothing to show'), 'warning');
        }
        return ($result);
    }
 
}

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

Codeflow

Пример в картинках

Локализация

Учитывая, что Ubilling используется на территории различных стран где используются различные языки, позаботьтесь о том, чтобы ваш код можно было легко локализировать. То-есть при выводе любых надписей, используйте штатную i18n функцию __('English string here'). Подробнее о том, как работает локализация вы можете узнать в базовом руководстве по разработке модулей. Как минимум от вас ожидается написание строк подлежащих локализации на английском с желательным самостоятельным переводом на украинский и русский или ваш нативный язык.

Логирование

При записи событий в общий системный лог, при помощи log_register() либо любыми другими способами, постарайтесь придерживаться следующих соглашений, в тексте записи:

  • (логин пользователя)
  • {логин администратора}
  • ((ID пользователя УКВ))
  • [цифровой ID чего либо]
  • `какое либо строковое значение`

Хороший пример:

log_register('MYMODULE CHANGE ('.$userLogin.') EYECOLOR ON `blue`');

или

log_register('MYMODULE CREATE ['.$newID.'] AS `cocaine`');

Документирование изменений

Если у вас есть доступ к редактированию этой wiki - документируйте изменения внесенные вами. Как минимум стоит добавить сведения о том что вы что-то сделали в Чейнджлог. Конечные пользователи должны знать, как измениться их жизнь в будущем. Изменения которые не видимы конечному пользователю и никак не влияют на его жизнь в принципе можно не описывать, хотя тоже можно в виде «проведен рефакторинг/чистка/оптимизация кода». Старайтесь сделать это кратко и понятно, желательно в одну строчку. Хорошими примерами могут быть такие описания произведенных вами изменений:

  • Модуль «Безысходность»: больше не вызывает массовой гибели перламутровых ежей.
  • Модуль «Тоталитаризм»: небольшой рефакторинг, добавлен показ списка жертв, увеличено быстродействие.

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

  • alter.ini: добавлена новая опция CHAINSAW_ENABLED предназначенная для включения техасской резни бензопилой.

Также в случае добавления новой опции, вы должны задокументировать ее на страницах, посвященных соответствующему файлу конфигурации, типа таких для alter.ini или userstats.ini где опции следует указывать со значениями по-умолчанию, а также в Рекомендациях к обновлению. Также не забываем заполнять опциями и их значениями по-умолчанию сами файлы обновлений для текущего релиза в content/updates/configs/

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

  1. Ваш модуль требует конфигурации, отличной от вкл/выкл
  2. Управляется несколькими опциями
  3. Работает в синергии с другими модулями
  4. Требует какой-то специфичной конфигурации
  5. Делает не очевидные стороннему человеку штуки

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

Пасхалки, отсылки и прочий фансервис

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

А где же Best Practices?

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

Контрибуция в Ubilling

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

Оформление Пуллреквестов

Для начала Вам необходимо создать ФОРК проекта.

Для этого нажмите верху страницы проекта кнопку Fork.

Это действие сделает копию исходного кода со всеми ветками в ваш акаунт. Это нам понадобится позже.

Далее на сервера выполните следующие действия:

git clone https://github.com/nightflyza/Ubilling
cd Ubilling
git remote add my-fork https://github.com/ВАШ_ЮЗЕРНЕЙМ_НА_GITHUB/Ubilling.git
git checkout -b master-some-fix
vi CONTRIBUTING.md
git add CONTRIBUTING.md
git commit -m "I add some line to CONTRIBUTING"
git push —set-upstream my-fork master-some-fix

Заходим на страницу вашего форка и видим, что Гитхаб предлагает вам создать пулреквест:

После создания пулреквеста - Github определит, если конфликт с официальным кодом или нет. Если конфликта нет, то просто ждем пока его смержат. Если конфликт есть - закрываем пулреквест и делаем все изменения по новой, только так, что-бы не получились снова конфликты. Ждем, пока мейнтейнер все смержит. Главное, чтобы ваши коммиты не пересекались с его изменениями, иначе тогда придется все по новой делать.

Дальше возвращаемся в ветку master официального проекта и обновляем уже смерженный код:

git checkout master
git pull

Теперь можно удалить ветку с вашего форка ( master-some-fix):

git push my-fork :master-some-fix
git branch -D master-some-fix
codingguidelines.txt · Последние изменения: 2020/04/22 13:32 — nightfly