Хорошо читаемый код, рекомендации и лучшие практики

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

Правила, которых придерживаюсь при написании веб приложений. Примеры на php, как доказательство возможности на нём нормально писать. Код для примеров я дёргал из реальных проектов, которыми занимался.

Как должен выглядеть хороший код? У меня накопились некоторые соображения на эту тему. Я согласен с тем мнением, что все текущие code style — говно, но лучше придерживаться их, чем не следовать никаким правилам вообще.

Фундамент для PHP это Zend Coding Style собственно его я и рекомендую с уточнениями и исключениями. Если вы его не видели, то стоит начать с прочтения и перейти дальше сюда.

Исключения по форматированию

SQL

SQL выборки так форматировать плохо. Пример:

$sql = "SELECT `id`, `name` FROM `people` "
     . "WHERE `name` = 'Susan' "
     . "ORDER BY `name` ASC ";

А так хорошо:

$sql = " 
    SELECT `id`, `name` FROM `people`
    WHERE `name` = 'Susan' 
    ORDER BY `name` ASC 
";

Массивы

Чтобы отформатировать так код нужно потратить много калорий :

$sampleArray = array(1, 2, 3, 'Zend', 'Studio',
                     $a, $b, $c,
                     56.44, $d, 500);

А так — нет:

$sampleArray = array(
    1, 2, 3, 'Zend', 'Studio',
    $a, $b, $c,
    56.44, $d, 500
);

Или так:

$sampleArray = 
    array(
        1, 2, 3, 'Zend', 'Studio',
        $a, $b, $c,
        56.44, $d, 500
    );

Я не знаю о мотивах авторов, которыми они руководствовались предложив варианты выше, но SQL и массивы больше трёх строк они явно не писали.

Личные предпочтения

1) Как говорил Ленин, на строку выделяется 80 символов и больше в неё писать не рекомендуется ни на каком языке, и он в принципе прав. 80 символов конечно не догма, если сильно хочется, то можно. Я понимаю что все современные девайсы уже давно дают гораздо больше пространства и эти заскорузлые правила с действительностью не соприкасаются. Но что до чтения длинного кода, то бегать глазами по всему экрану тоже не очень приятно.

2) Табы или пробелы? Абсолютно всё равно, главное придерживаться в проекте одного стиля и не мешать всё подряд. Стандарты по умолчанию считают что вы обязательно налажаете, отсюда строгие правила про пробелы.

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

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

Уточнения по форматированию

1) Код не влазит в строку? Return, Tab и пишем дальше. Пример формы:

$form =
    Form::create()->
    add(
        Primitive::choice('type')->
        setList(
            array(
                'city' => 'kladr_kladr',
                'street' => 'kladr_street'
            )
        )->
        setDefault('city')->
        optional()
    )->
    import($request->getGet())->
    add(
        Primitive::string('letter')->
        setMin(1)->
        setMax(64)->
        required()
    )->
    add(
        Primitive::string('city')->
        setMin(1)->
        setMax(64)->
        required()
    )->
    add(
        Primitive::string('street')->
        setMin(1)->
        setMax(64)->
        required()
    )->
    importMore($request->getPost());

Переносы целесообразно делать по оператору присвоения "=", вызовам методов "->" и скобкам "()".

2) А так выглядят тернарные операторы и конкатенация:

$objectList[$data['code']] = 
    $data['name']
    .(
        ($type == 'kladr_street') 
            ? ' ' . $data['socr'] . '.' 
            : null
    );

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

3) Использование фигурных скобок {} в if, else не догма, если у вас одна операция на условие и вы понимаете как правильно это использовать, то утруждать себя набором этих скобок нет никакого смысла. Опять же разбираемся почему код стайлы запрещают писать без скобок. Причина есть, пример:

if ($kladr->getSocr() == 'р-н')
    if ($region = getRegionByResidence($kladr->getCode()))
        $name .= ', ' . $region;
else 
    if ($district = getDistrictByResidence($kladr->getCode()))
        $name .= $district;

Вроде бы всё хорошо, но интерпретатор будет выполнять всё подряд, и else отработает в паре со вторым if. Поэтому, в таких случаях стоит использовать фигурные скобки:

if ($kladr->getSocr() == 'р-н') {
    if ($region = getRegionByResidence($kladr->getCode()))
        $name .= ', ' . $region;
} else {
    if ($district = getDistrictByResidence($kladr->getCode()))
        $name .= $district;
}

Следующий уровень вполне без них обходится. Мне нравится Python стиль, он хорошо читается и не требует лишних движений. foreach кстати тоже хорошо работает без фигурных скобок.

4) Управляющие конструкции if, switch, foreach, for, while, return находящиеся на одном уровне отделяются друг от друга новой строкой.

$city = 
    is_numeric($form->getValue('city'))
        ? 
            $form->getValue('city')
        : 
            KladrKladr::dao()->getCityCodeByLetter(
                $form->getValue('city')
            );

if ($city)
    $expression =
        Expression::andBlock(
            Expression::like(
                'name',  $letter . '%'
            ),
            Expression::like(
                'code',  substr($city, 0, strlen($city) - 2) . '%'
            )
        );

5) Если управляющая структура использует фигурные скобки, то внутри, блоки отделяем новой строкой, после открывающей и перед закрывающей:

if (isset($expression)) {

    $query =
        OSQL::select()->
        multiGet('name', 'socr', 'code')->
        where(
            $expression
        )->
        from($type);

    $result = DBPool::getByDao(Member::dao())->querySet($query);

    foreach ($result as $data)
        $objectList[$data['code']] = 
            $data['name']
            .(
                ($type == 'kladr_street') 
                    ? ' ' . $data['socr'] . '.' 
                    : null
            );

    echo json_encode($objectList);

}

Переменные

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

Пример:

$param = blaBla($someVariable);
$bla = functionName($param);

Хорошо:

$bla = functionName(
    blaBla($someVariable)
);

Если дальше есть ещё код, то имеет смысл вынести на новую строку:

$bla = 
    functionName(
        blaBla($someVariable)
    )->
    someMethod();

Если вы знаете чем занимается функция functionName, то не важно как она называется, если нет, то из названия всё равно непонятно какие данные она вернёт и какого типа.

Методы

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

Стив Макконнелл рекомендует выделять каждый чих в метод, а я не рекомендую этого делать без надобности, нет ничего страшного в километровой портянке, если это сэкономит вам время. Если есть желание без особой надобности разбить большой блок по функциям, чтобы улучшить читаемость, это хорошая затея, но смысла бить блоки на куски меньше чем влазит на экран &mdashl; нет. Если вы пишете контроллер с обычно выборкой из базы, то тем более, да он большой, но тут то ведь всё понятно.

Именование булевых методов как тут, да и вообще рекомендую эту статью.

isSet, isVisible, isFinished, isFound, isOpen

и

bool hasLicense();
bool canEvaluate();
bool shouldSort();

Что раздражает не менее, теперь в CSS

Никогда не стоит делать вот так:

#content .speed-b .info{margin:0 0 20px;font-size:0.8em}
#content .speed-b .info th{width:146px;padding:0 12px 10px;text-align:left;font-weight:normal;color:#555}

На каждое вложение таб и новая строка, все свойства одно под другим, в алфавитном порядке:

#content .speed-b .info{
    font-size:0.8em
    margin:0 0 20px;
}
    #content .speed-b .info th{
        color: #555;
        font-weight: normal;
        padding: 0 12px 10px;
        text-align: left;
        width:146px; 
    }

Многоэкранный HTML

Плохо:

<form class="settings" enctype="multipart/form-data" id="tForm" name="tForm" action="<?=PATH_WEB?>?area=ispBaseViewer" method="post">

Хорошо:

<form class="settings" 
    enctype="multipart/form-data" 
    id="tForm" 
    name="tForm" 
    action="<?=PATH_WEB?>?area=ispBaseViewer" 
    method="post">

Если в алфавитном порядке то ещё и лучше, но не догма, тут как правило небольшое поле для поиска.

Javascript

Можно как попало, а нужно хотя бы как-то так :

$(document).ready(function() {
    $('form.agreement select').change(function() {
        var loader = $(this).parent().find('.loader');
        loader.show();
        $.post(
            '/?area=ajaxIspBaseFileAgreementSaver', 
            {
                id: $(this).parent().find('[name="id"]').val(),
                kladrId: $(this).val()
            }
        ).done(function(data) {
            loader.hide();
        });
    });
});

Когда код перестает читаться, форматируем как в примере с php.

Шаблоны

Сначала <?php, не в середине строки, а всегда первым. HTML форматируется по своим отступам, php по своим.

    <fieldset>
<?php
    if ($ispBaseFileAgreementList ) {
        foreach ($ispBaseFileAgreementList as $ispBaseFileAgreement) {
?>
        <p>тут html со своими<?=$ispBaseFileAgreement->getName()?> отступами</p>
<?php 
        }
    }
?>
    </fieldset>

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

Как всегда не получилось объять всё что хотелось, но по возможности буду обновлять и дополнять эту страницу, лучше так чем никак.

И рекомендуемые линки по теме:

Вот это PSR-2 и Google Codestyle


comments powered by Disqus