Користувальницькькі налаштування

Налаштування сайту


Сайдбар

Розділи

Загальний опис
Історія змін
Рекомендації до оновлення
Плани на майбутнє
Відомі проблеми
Онлайн демо
Відео
Допомога проекту
Люди
Трохи про безпеку

FAQ



Редагувати сайдбар

codingguidelines

Ubilling Coding Guidelines & Best Practices або біблія Ubilling

Навіщо це?

Наш проєкт є некомерційним, а найголовніше відкритим. Що означає, що окрім того, що вашим кодом мають і користуватимуться інші люди, ще якісь інші люди намагатимуться його читати, намагатимуться зрозуміти, дебажитимуть і рефакторитимуть. Було б дуже добре, щоб у цих людей при цьому не лилася кров з очей і не траплялися спонтанні спроби суїциду. Тому важливо щоб ваш код був максимально:

  • Читабельним
  • Оптимізованим
  • Тестованим
  • Коментованим
  • Модульним

Коротко будемо називати ці критерії Ч.О.Т.К.М.. Якщо такий набір правил здається вам занадто складним, ви можете намагатися слідувати спрощеному набору критеріїв, яким повинен відповідати ваш код:

  • Простота
  • Ідіоматичність
  • Збереження легасі
  • Документованість
  • Адаптованість
  • Тестованість
  • Оптимізованість

Для стислості будемо називати його П.І.З.Д.А.Т.О., що характеризуватиме те, як він повинен би виглядати.

Error reporting

Перед тим, як почати щось писати, переконайтеся, що писати ви будете свідомо не наосліп, а щось осмислене, що матиме шанс працювати в когось іще, крім вас.

Для початку запам'ятовуємо, що у більшості користувачів php.ini має такий вигляд:

php.ini
magic_quotes_gpc = Off 
magic_quotes_runtime = Off 
magic_quotes_sybase = Off 
date.timezone="Europe/Kiev"
 
display_errors = On
display_startup_errors = On
log_errors = On
log_errors_max_len = 1024
report_memleaks = On
html_errors = On
memory_limit = 512M
max_input_vars = 50000
 
post_max_size = 64M
upload_max_filesize = 64M

Відповідно у вас він має виглядати аналогічним чином. Якщо ви не бачите купи 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 та Visual Studio Code і рефлекторно натискає Alt+Shift+F для автоматичного форматування коду.

Іменування змінних та констант

Постарайтеся, щоб оголошені вами змінні своїм виглядом показували, що вони таке і навіщо вони потрібні, а в ідеалі символізували, що всередині них повинно знаходитися. Допустимими є lowerCamelCase і snake_case. Найкращим - lowerCamelCase. Постарайтесь також уникнути ubogogo_translitu, ischeBilshUbogogoTranslitu та стилю “ЧмАфКи ФсЄм КтО в еТОМ чАті”. Для констант можете використовувати КАПС або ЗМІЇНИЙ_КАПС.

Приклади гарного іменування змінних:

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

І ось у якому вигляді категорично не хочеться бачити роботу зі змінними та їх іменуванням:

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

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

$i = 0; // стандартний інкрементний або декрементний лічильник, ну там для $i++, наприклад.
$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();
    }
 
    protected function loadData() {
        .....
    }
 
    public function renderList() {
        .....
    }
 
    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 та PHP 8.2 з PHP 8.3 (так, це можливо і зовсім не складно), через велику кількість старих установок, які міняти та перевстановлювати на проді, ніхто не буде, так як вони успішно працюють роками. Тому утримайтеся як від використання нового синтаксису масивів із квадратними брекетами у вигляді

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

І використовуйте старий і загалом зручний формат оголошення масивів у вигляді

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

Ні, це не стосується використання автоінкрементних квадратних дужок при заповненні даних у масивах, воно працює нормально де завгодно:

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

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

У разі, якщо вам потрібно змінити наявний код, що вже працює, переконайтесь, що його поведінка радикально не зміниться. Якщо вам потрібно додати якісь параметри до функції або методу - додавайте їх наприкінці оголошення функції, за можливістю вказуйте дефолтні значення, і переконайтеся, що з ними чи без них, функція залишиться працездатною і збереже свою попередню поведінку. Якщо з якихось причин це неможливо, ви зобов'язані переглянути всі місця, де використовується ця функція або метод, доповнити їх передачею правильних параметрів, і переконатися в адекватному функціонуванні всіх цих місць.

Якщо ви пишете щось поруч з існуючим кодом, що в основному повторює його функціональність, але несе підтримку іншого функціоналу, постарайтеся слідувати ідеології та стилю оригіналу. Цілком можливо, що хтось за вами буде це лагодити і допилювати. Не потрібно викликати в нього когнітивний дисонанс.

Коментування коду

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

/**
 * Implementation of funeral services with graves management
 */
 
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 функцію __('String in English 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 · Востаннє змінено: 2024/01/26 18:39 повз nightfly