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) - значит с вами и вашим кодом что-то не так, можете его спрятать и никому не показывать. Пускай умрет в одиночестве.

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

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

Особых требований нет. Рекомендуемой расстановкой фигурных скобок в условиях 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 для имен классов и общим для переменных 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() {
        .....
}

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

Просто загляните в файлы конфигурации типа 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.0, из за большого количества старых установок, которые менять никто не будет, так как они успешно работают годами. Поэтому воздержитесь как от использования нового синтаксиса массивов с квадратными брекетами в виде

$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 - там и без комментариев все очевидно.

Локализация

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

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

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

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

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

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

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

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

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

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

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

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

А где же Best Practices?

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

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

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

Для этого нажмите верху страницы проекта кнопку 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 · Последние изменения: 2018/06/14 18:11 — nightfly
 
За исключением случаев, когда указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki