Какова цель использования фигурных скобок (т.е. {}) для однострочного цикла if или?

321

Я читаю некоторые лекционные заметки моего лектора C ++, и он написал следующее:

  1. Использовать отступ // OK
  2. Никогда не полагайтесь на приоритет оператора - всегда используйте скобки // OK
  3. Всегда используйте блок {} - даже для одной строки // не в порядке , почему ???
  4. Const объект на левой стороне сравнения // OK
  5. Используйте unsigned для переменных, которые> = 0 // хороший трюк
  6. Установите Pointer в NULL после удаления - Двойная защита удаления // не плохо

Третий метод мне не понятен: что я получу, поместив одну строку в a { ... }?

Например, возьмите этот странный код:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

и заменить его на:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

В чем преимущество использования 1-й версии?

JAN
источник
256
Читаемость и удобство обслуживания. Не сразу очевидно, к какому блоку операторов относится j ++, и что добавление кода после него не будет связано с оператором if.
Лес и деревья
27
Мне всегда говорили использовать фигурные скобки {} для этих строк по нескольким причинам. Это делает код более понятным для чтения. Кроме того, кому-то еще через шесть месяцев может понадобиться отредактировать ваш код, так что ясность важна, и с помощью фигурных скобок вероятность возникновения ошибки будет меньше. В этом нет ничего более правильного, это просто вопрос хорошей практики. Имейте в виду, что проект может иметь тысячи и тысячи строк кода, через которые может пролиться какой-то новый парень!
RossC
30
Я не согласен с 6, поскольку он скрывает двойное удаление и потенциально скрывает логические ошибки.
Лучиан Григоре
28
# 5 может быть сложно - рассмотреть этот цикл: for (unsigned i = 100; i >= 0; --i).
Арчи
36
Кстати, (i % 2 == 0)противоречит (2). Вы полагаетесь на приоритет оператора, и значение, конечно, ((i % 2) == 0)а не (i % (2 == 0)). Я бы классифицировал правило 2 как «правильное мнение, но« всегда »неправильно».
Стив Джессоп

Ответы:

509

Попробуем также изменить, iкогда мы увеличиваем j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

о нет! Исходя из Python, это выглядит хорошо, но на самом деле это не так, поскольку это эквивалентно:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

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

Другая очень веская причина указана в ответе ta.speot.is .

Третий, который я могу придумать, является вложенным if:

if (cond1)
   if (cond2) 
      doSomething();

Теперь предположим, что вы хотите, чтобы doSomethingElse()когда cond1не встретил (новая функция). Так:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

что, очевидно, неправильно, поскольку elseассоциируется с внутренним if.


Изменить: так как это привлекает некоторое внимание, я уточню свою точку зрения. Вопрос, на который я отвечал:

В чем преимущество использования 1-й версии?

Который я описал. Есть некоторые преимущества. Но, IMO, «всегда» правила не всегда применяются. Так что я не полностью поддерживаю

Всегда используйте блок {} - даже для одной строки // не в порядке, почему ???

Я не говорю, всегда используйте {}блок. Если это достаточно простое условие и поведение, не надо. Если вы подозреваете, что кто-то может прийти позже и изменить свой код, чтобы добавить функциональность, сделайте.

Лучиан Григоре
источник
5
@Science_Fiction: правда, но если вы добавите i++ раньше j++ , то обе переменные будут по-прежнему в области видимости, когда они будут использоваться.
Майк Сеймур
26
Это звучит очень разумно, но игнорирует тот факт, что редактор делает отступ, а не вы, и он будет делать отступ i++;таким способом, который сразу показывает, что он не является частью цикла. (В прошлом это могло быть разумным аргументом, и я видел такие проблемы. Около 20 лет назад. Не с тех пор.)
Джеймс Канз
43
@James: это не «факт», но это ваш рабочий процесс. И рабочий процесс многих людей, но не всех. Я не думаю, что это обязательно ошибка - рассматривать исходный код C ++ как простой текстовый файл, а не вывод редактора WYSIWYG (vi / emacs / Visual Studio), который обеспечивает соблюдение правил форматирования. Таким образом, это правило не зависит от редактора, кроме того, что вам нужно, но не за пределами того, что люди фактически используют для редактирования C ++. Отсюда и «оборонительный».
Стив Джессоп
31
@JamesKanze Вы действительно полагаетесь на предположение, что все всегда работают в мощных IDE? Последний КИ был написан в Нано. Даже учитывая это, одной из первых вещей, которые я склонен отключать в IDE, является автоиндентирование - потому что IDE мешает моему нелинейному рабочему процессу, пытаясь исправить мои «ошибки» на основе неполной информации , Среды IDE не очень хороши для автоматического выравнивания естественного потока каждого программиста. Те программисты, которые используют такие функции, как правило, объединяют свой стиль со своей IDE, что хорошо, если вы используете только одну IDE, но не слишком много, если вы работаете со многими.
Rushyo
10
«Это глупая ошибка, но она может допустить даже опытный программист». - Как я уже сказал в своем ответе, я не верю этому. Я думаю, что это полностью надуманный случай, который не представляет проблемы в реальности.
Конрад Рудольф
324

Очень легко случайно изменить поток управления с комментариями, если вы не используете {и }. Например:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Если вы закомментируете do_something_else()однострочный комментарий, вы получите следующее:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Он компилируется, но must_always_do_this()не всегда вызывается.

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

ta.speot.is
источник
3
Ооо мальчик !! это определенное поведение, которое must_always_do_this();будет выполняться, если вы прокомментируете // do_something_else ();
Анубис
1
@Supr, как было написано вначале, говорит, что трудно сломать правильный поток, если вы используете фигурные скобки, а затем приводит пример того, как легко нарушить код без правильных скобок
SeanC
20
Я столкнулся с этим только на днях. if(debug) \n //print(info);, В основном вытащили целую библиотеку.
Кевин
4
Fortunately we caught it in code review.Ой! Это звучит так неправильно. Fortunately we caught it in unit tests.было бы намного лучше!
BЈовић
4
@ BЈовић Но что, если код был в модульном тесте? Разум поражает. (Шучу, это устаревшее приложение. Нет юнит-тестов.)
ta.speot.is
59

У меня есть сомнения относительно компетентности лектора. Учитывая его пункты:

  1. хорошо
  2. Кто-нибудь действительно писал (или хотел читать) (b*b) - ((4*a)*c)? Некоторые приоритеты очевидны (или должны быть), а лишние скобки только добавляют путаницы. (С другой стороны, вы должны использовать скобки в менее очевидных случаях, даже если вы знаете, что они не нужны.)
  3. Вроде, как бы, что-то вроде. Существует два распространенных соглашения для форматирования условий и циклов:
    if (cond) {
        код;
    }
    
    и:
    если (конд)
    {
        код;
    }
    
    Во первых я бы с ним согласился. Открытие {не так заметно, поэтому лучше предположить, что оно всегда там. Во втором, однако, у меня (и большинства людей, с которыми я работал) нет проблем с опущением скобок для одного утверждения. (При условии, конечно, что отступы являются систематическими и что вы используете этот стиль последовательно. (И многие очень хорошие программисты, пишущие очень читаемый код, опускают фигурные скобки даже при форматировании первым способом.)
  4. NO . Такие вещи if ( NULL == ptr )достаточно уродливы, что мешают читабельности. Напишите сравнения интуитивно. (Что во многих случаях приводит к константе справа.) Его 4 - плохой совет; все, что делает код неестественным, делает его менее читабельным.
  5. NO . Все, кроме intзарезервированных для особых случаев. Для опытных программистов на C и C ++ используются unsignedсигналы битовых операторов. C ++ не имеет реального кардинального типа (или любого другого эффективного поддиапазонного типа); unsignedне работает для числовых значений, из-за правил продвижения. Предполагается, что числовые значения, для которых не имеют смысла никакие арифметические операции, как, например, серийные номера unsigned. Однако я бы поспорил с этим, потому что он посылает неверное сообщение: побитовые операции тоже не имеют смысла. Основное правило заключается в том, что целочисленные типы int, _unless_, есть существенная причина для использования другого типа.
  6. NO . Делать это систематически вводит в заблуждение и фактически ничего не защищает. В строгом коде ОО delete this;часто это наиболее частый случай (и вы не можете установить thisего NULL), а в остальном, большинство из них delete- в деструкторах, поэтому вы все равно не сможете получить доступ к указателю позже. И установка его на NULLничего не делает с другими указателями, плавающими вокруг. Систематическая установка указателя NULLдает ложное чувство безопасности и на самом деле ничего вам не дает.

Посмотрите на код в любой из типичных ссылок. Страуструп нарушает каждое правило, которое вы дали, например, кроме первого.

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

Джеймс Канзе
источник
13
Число 4 может быть некрасивым, однако в этом есть цель. Он пытается предотвратить, если (ptr = NULL). Я не думаю, что когда-либо использовал delete this, это более распространено, чем я видел? Я не склонен думать, что установка указателя на NULL после использования - плохая вещь, кроме YMMV. Может быть, это только я, но большинство его рекомендаций не кажутся такими уж плохими.
Firedragon
16
@Firedragon: Большинство компиляторов будут предупреждать, if (ptr = NULL)если вы не напишите это как if ((ptr = NULL)). Должен согласиться с Джеймсом Канзе, что безобразие наличия NULLсначала делает для меня это НЕТ.
Лев
34
@JamesKanze: Я должен сказать, что я не согласен с большей частью того, что вы здесь изложили - хотя я ценю и уважаю ваши аргументы в пользу их получения. Для опытных программистов на C и C ++ использование битовых сигналов без знака. - Я совсем не согласен: использование битовых операторов сигнализирует об использовании битовых операторов. Для меня использование unsignedуказывает на стремление со стороны программиста, что переменная должна представлять только положительные числа. Смешивание со знаковыми числами обычно вызывает предупреждение компилятора, что, вероятно, было тем, что намеревался лектор.
Компонент 10
18
Для опытных программистов на C и C ++ использование беззнаковых сигналов битовых операторов или нет. size_t, кто угодно?
ta.speot.is
16
@ Джеймс Канзе, подумай о цели. Вы сравниваете код, созданный опытным программистом, с учебными примерами. Эти правила предоставляются лектором, потому что они видят ошибки, которые он видит в своих учениках. С опытом студенты могут расслабиться или игнорировать эти абсолюты.
Джошуа Шейн Либерман
46

Все остальные ответы защищают правило вашего лектора 3.

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

Ответ «Компонента 10» фактически выдвигает на первый план единственно возможный случай, когда это действительно может привести к ошибке. Но, с другой стороны, замена кода с помощью регулярных выражений всегда требует огромной осторожности.

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

Рассмотрим функцию с множеством защитных предложений в начале (и да, следующее - плохой код C ++, но в других языках это было бы довольно распространенной ситуацией):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

Это ужасный код, и я настоятельно утверждаю, что следующее гораздо более читабельно:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Аналогично, короткие вложенные циклы выигрывают от того, что опускаются фигурные скобки:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Сравнить с:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

Первый код является кратким; второй код раздутый.

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

Короче говоря: не пишите ненужный код, который занимает место на экране.

Конрад Рудольф
источник
26
Если вы не верите в написание кода, который занимает ненужное место на экране, тогда вам не стоит ставить открывающую скобку на отдельной строке. Теперь мне, вероятно, придется пригнуться и убежать от святой мести GNU, но серьезно - либо вы хотите, чтобы ваш код был вертикально компактным, либо нет. И если вы это сделаете, не делайте вещи, предназначенные исключительно для того, чтобы сделать ваш код менее вертикально компактным. Но , как вы говорите, зафиксировав , что вы по- прежнему также необходимо удалить лишние скобки. Или, возможно, просто написать if (a == nullptr) { throw null_ptr_error("a"); }в одну строку.
Стив Джессоп
6
@Steve В самом деле, я бы поставил открывающую фигурную скобку на предыдущей строке, по той причине , вы заявили. Я использовал другой стиль здесь, чтобы сделать более очевидным, насколько экстремальной может быть разница.
Конрад Рудольф
9
+1 Я полностью согласен, что ваш первый пример намного легче читать без брекетов. Во втором примере мой личный стиль кодирования - использовать фигурные скобки во внешнем цикле for, а не во внутреннем. Я не согласен с @SteveJessop по поводу необходимости быть одним из крайностей в отношении вертикально компактного кода. Я опускаю дополнительные скобки с однострочниками, чтобы уменьшить вертикальное пространство, но я помещаю свои открывающие скобки на новую линию, потому что мне легче видеть прицел, когда скобки выстроены в линию. Цель - удобочитаемость, и иногда это означает использование большего вертикального пространства, в других случаях это означает использование меньшего количества.
Travesty3
22
«Я никогда не сталкивался с этой проблемой в реальной жизни»: вам повезло. Подобные вещи не просто обжигают вас, они дают вам ожоги третьей степени на 90% (и это всего лишь несколько уровней управления, требующих исправления поздно вечером).
Ричард
9
@Richard Я просто не покупаю это. Как я объяснил в чате, даже если эта ошибка когда-либо произойдет (что я нахожу маловероятной), ее тривиально исправить, если вы посмотрите на трассировку стека, потому что очевидно, где ошибка - просто глядя на код. Ваша преувеличенная претензия совершенно безосновательна.
Конрад Рудольф
40

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

Наиболее частый проблемный пример, с которым я столкнулся, это:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

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

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Учитывая, что добавление скобок занимает ~ 1 секунду и может сэкономить вам как минимум несколько запутанных минут отладки, почему бы вам никогда не воспользоваться опцией уменьшенной неоднозначности? Похоже на ложную экономию для меня.

варенье
источник
16
Разве проблема не в этом примере в неправильном отступе и слишком длинных строках, а не в скобках?
Хонза Брабек
7
Да, но следование рекомендациям по дизайну / кодированию, которые являются «безопасными» только в том случае, если предположить, что люди следуют и другим правилам (таким как отсутствие слишком длинных линий), кажется, вызывает проблемы. Если бы брекеты были с самого начала, в этой ситуации было бы невозможно получить неправильный блок if.
джем
16
Как бы добавить фигурные скобки (чтобы это if(really long...editor){ do_foo;}помогло вам избежать этого случая? Похоже, что проблема останется прежней. Лично я предпочитаю избегать фигурных скобок, когда в этом нет необходимости, однако это никак не связано со временем, необходимым для их написания, но снижает читаемость из-за двух дополнительных строк в коде
Grizzly
1
Хорошая мысль - я предполагал, что принудительное использование фигурных скобок также приведет к тому, что они будут помещены в разумное место, но, конечно, кто-то, решивший сделать вещи сложными, может поместить их в линию, как в вашем примере. Я думаю, что большинство людей не будет, хотя.
джем
2
Первое и последнее, что я делаю, касаясь файла, - нажимаю кнопку автоформатирования. Это устраняет большинство из этих проблем.
Джонатан Аллен
20

Мой 2с:

Использовать отступ

очевидно

Никогда не полагайтесь на приоритет оператора - всегда используйте скобки

Я бы не использовал слова «никогда» и «всегда», но в целом я считаю, что это правило полезно. В некоторых языках (Lisp, Smalltalk) это не проблема.

Всегда используйте блок {} - даже для одной строки

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

Const объект на левой стороне сравнения

Йода условия? Пожалуйста нет. Это вредит читабельности. Просто используйте максимальный уровень предупреждения при компиляции кода.

Используйте unsigned для переменных, которые> = 0

ХОРОШО. Забавно, но я слышал, что Страуструп не согласен.

Установите Pointer на NULL после удаления - Двойная защита от удаления

Плохой совет! Никогда не указатель, который указывает на удаленный или несуществующий объект.

Неманья Трифунович
источник
5
+1 только за последний пункт в одиночку. Необработанный указатель в любом случае не имеет бизнеса, владеющего памятью.
Конрад Рудольф
2
Что касается использования unsigned: не только Страуструп, но и K & R (в C), Херб Саттер и (я думаю) Скотт Мейерс. На самом деле, я никогда не слышал, чтобы кто-то, кто действительно понимал правила C ++, доказывал необходимость использования unsigned.
Джеймс Канз
2
@JamesKanze На самом деле, в тот же случай, когда я услышал мнение Страуструпа (Бостонская конференция в 2008 году), Херб Саттер был там и не согласился с Бьярном на месте.
Неманя Трифунович
3
Просто для завершения «не unsignedработает», одна из проблем состоит в том, что когда C ++ сравнивает подписанные и неподписанные типы одинакового размера, он выполняет преобразование в неподписанный тип перед выполнением сравнения. Что приводит к изменению стоимости. Преобразование в подписанное не обязательно будет намного лучше; сравнение должно действительно происходить «как если бы» оба значения были преобразованы в больший тип, который мог бы представлять все значения в любом типе.
Джеймс Канз
1
@ SteveJessop Я думаю, что вы должны принять это в контексте возврата функции unsigned. Я уверен, что у него нет проблем с exp(double)возвратом значения больше, чем MAX_INT:-). Но еще раз, реальная проблема - неявные преобразования. int i = exp( 1e6 );совершенно корректно C ++. В какой-то момент Страуструп предложил отказаться от неявных преобразований с потерями, но комитет не заинтересовался. (Интересный вопрос: будет ли unsigned-> intсчитаться с потерями. Я бы рассмотрел и unsigned-> intи int-> unsignedс потерями. Что бы иметь большое значение для unsignedОК
Джеймс Канз
18

это более интуитивно понятно и легко понятно. Это проясняет намерение.

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

Alok Save
источник
Makes the intent clear+1, это, пожалуй, самая краткая и точная причина.
Qix - МОНИКА ПРОИЗОШЛА
13

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

setCompIds( const std::string& compId, const std::string& compSubId );

тогда как для замены потребовалось два вызова:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Я решил изменить это с помощью регулярных выражений, что было очень успешно. Мы также передали код через astyle , что действительно сделало его намного более читабельным. Затем, частично пройдя через процесс обзора, я обнаружил, что при некоторых условных обстоятельствах это менялось так:

if ( condition )
   setCompIds( compId, compSubId );

К этому:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

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

Я заметил, что у astyle теперь есть опция, --add-bracketsкоторая позволяет вам добавлять скобки там, где их нет, и я настоятельно рекомендую это, если вы когда-нибудь окажетесь в том же положении, что и я.

Компонент 10
источник
5
Однажды я увидел документацию, в которой была замечательная чеканка "Microsoftligent". Да, с глобальным поиском и заменой можно совершать существенные ошибки. Это просто означает, что глобальный поиск и замена должны использоваться разумно, а не микросоюзно.
Пит Беккер
4
Я знаю, что это не моя посмертная работа, но если вы собираетесь делать замену текста в исходном коде, то вы должны делать это в соответствии с теми же правилами, которые вы использовали бы для того типа замены текста, который хорошо установлен. на языке: макросы. Вы не должны писать макрос #define FOO() func1(); \ func2(); (с переводом строки после обратной косой черты), то же самое относится и к поиску и замене. Тем не менее, я видел «всегда использовать фигурные скобки», продвинутые в качестве правила стиля именно потому, что это избавляет вас от оборачивания всех ваших макросов с несколькими операторами do .. while(0). Но я не согласен.
Стив Джессоп
2
Между прочим, это "устоявшаяся" идея в том смысле, что японский горбыль устоялась: я не говорю, что мы должны изо всех сил использовать макросы и замену текста, но я говорю, что когда мы делаем такие Дело в том, что мы должны делать это таким образом, чтобы это работало, а не делало что-то, что работает, только если определенное правило стиля было успешно наложено на всю кодовую базу :-)
Стив Джессоп
1
@ SteveJessop Можно также поспорить за подтяжки и пояс. Если вам нужно использовать такие макросы (как мы это делали до C ++ и inline), то вам, вероятно, следует стремиться к тому, чтобы они работали как функция, насколько это возможно, используя do { ... } while(0)при необходимости хитрость (и множество дополнительных скобок. Но это все равно не помогло бы). не мешать вам использовать фигурные скобки везде, если это стиль дома (FWIW: я работал в местах с различными стилями дома, охватывая все стили, обсуждаемые здесь. Я никогда не находил, что какой-либо серьезной проблемой.)
Джеймс Канз
1
И я считаю, что чем больше стилей вы работали, тем внимательнее вы читали и редактировали код. Таким образом, даже если у вас есть предпочтение, что легче всего читать, вы все равно будете успешно читать остальные. Я работал в компании, где разные компоненты были написаны разными командами в разных «стилях дома», и правильное решение - жаловаться на это в обеденном зале без особого эффекта, а не пытаться создать глобальный стиль :-)
Стив Джессоп
8

Я использую {}везде, кроме нескольких случаев, когда это очевидно. Одна строка - это один из случаев:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

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

Другой пример в C # с использованием statment

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

что эквивалентно

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}
Лукаш Мадон
источник
1
Просто используйте запятые в usingзаявлении, и вам не нужно :)
Ry-
1
@minitech Это просто не работает - вы можете использовать запятую только тогда, когда типы равны, но не для неравных типов. Способ Лукаса сделать это - канонический способ, IDE даже форматирует это по-другому (обратите внимание на отсутствие автоматического отступа для второго using).
Конрад Рудольф
8

Самый подходящий пример, который я могу представить:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

Что ifбудет в elseпаре с? Отступ подразумевает, что внешний ifполучает else, но на самом деле это не так, как его видит компилятор; внутренний if будет получить else, а внешний ifне делает. Вы должны знать это (или видеть, как он ведет себя таким образом в режиме отладки), чтобы выяснить путем проверки, почему этот код может не соответствовать вашим ожиданиям. Это становится более запутанным, если вы знаете Python; в этом случае вы знаете, что отступ определяет блоки кода, поэтому вы ожидаете, что он будет оцениваться в соответствии с отступом. C #, однако, не дает пролистывать пробелы.

Тем не менее, я не особо согласен с этим правилом «всегда используйте скобки». Это делает код очень шумным в вертикальном направлении, снижая возможность его быстрого чтения. Если утверждение:

if(someCondition)
   DoSomething();

... тогда это должно быть написано так же, как это. Выражение «всегда использовать скобки» звучит как «всегда заключать математические операции в круглые скобки». Это превратит очень простое утверждение a * b + c / dв ((a * b) + (c / d))возможность пропустить близкого человека (проклятие многих кодеров) и для чего? Порядок операций хорошо известен и строго соблюдается, поэтому круглые скобки являются избыточными. Вы должны использовать круглые скобки только для обеспечения другого порядка операций, чем обычно применяется: a * (b+c) / dнапример. Брекеты похожи; используйте их, чтобы определить, что вы хотите сделать в тех случаях, когда это отличается от значения по умолчанию и не является «очевидным» (субъективным, но обычно довольно здравым смыслом).

Keiths
источник
3
@AlexBrown ... это была моя точка зрения. Правило, изложенное в OP, гласит: «всегда используйте скобки, даже для отдельных строк», с чем я не согласен по той причине, о которой я говорил. Скобки помогли бы с самым первым примером кода, потому что код не будет вести себя так, как с отступом; вам придется использовать скобки для сопряжения elseс первым ifвместо второго. Пожалуйста, удалите понижающий голос.
KeithS
5

Просматривая ответы, никто не указал явно, какую практику я привыкаю, рассказывая историю вашего кода:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

становится:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Помещение строки в j++ту же строку, что и if, должно сигнализировать кому-либо еще: «Я хочу, чтобы этот блок только увеличивался j» . Из coursethis имеет смысл только , если строка выглядит упрощенно , насколько это возможно, потому что поставив точку останова здесь, как пери упоминает, не собирается быть очень полезным.

На самом деле, я только что натолкнулся на часть API-интерфейса Twitter Storm, который имеет такой «код» в java, вот соответствующий фрагмент кода для выполнения на странице 43 этого слайд-шоу :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

В блоке for есть две вещи, поэтому я бы не стал использовать этот код. Т.е. никогда :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

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

Если вы действительно хотите телеграфировать кому-то еще, это работа в одну строку, используйте троичного оператора или ?:форму:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Но это граничит с code-golf, и я думаю, что это не очень хорошая практика (мне не ясно, стоит ли мне ставить j ++ на одну сторону :или нет). NB Я раньше не запускал троичный оператор в C ++, я не знаю, работает ли он, но он существует .

Коротко:

Представьте, как ваш читатель (т. Е. Человек, обслуживающий код) интерпретирует вашу историю (код). Сделайте это как можно более ясным для них. Если вы знаете, что начинающий программист / студент поддерживает это, возможно, даже оставьте как {}можно больше, просто чтобы они не запутались.

Pureferret
источник
1
(1) Помещение выражения в одну строку делает его менее читабельным, а не более. Особенно простые мысли, как приращение, легко упускаются из виду. Do положить их на новой строке. (2) Конечно, вы можете поместить свой forцикл в одну строку, почему это не должно работать? Это работает по той же причине, по которой вы можете опустить скобки; Новая строка просто не имеет значения в C ++. (3) Ваш пример условного оператора, кроме ужасного, является недействительным C ++.
Конрад Рудольф
@KonradRudolph спасибо, я немного заржавел на C ++. Я никогда не говорил (1) был более удобным для чтения, но это будет означать быть , что этот кусок кода был имел в виду , чтобы быть онлайн в одной строке. (2) Мой комментарий заключался в том, что я не смог бы прочитать его и знать, что он работает, будь то вообще или как предполагалось; это пример того, чего не следует делать по этой причине. (3) Спасибо, я давно не писал C ++. Я исправлю это сейчас.
Pureferret
2
Кроме того, размещение более одного выражения в одной строке усложняет отладку кода. Как поставить точку останова на втором выражении в этой строке?
Петр Перак
5

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

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

Выглядит хорошо. Проблема с этим действительно легко пропустить, особенно когда функция, содержащая код, намного больше. Проблема в том, что goto failзапускается безоговорочно. Вы можете легко представить, как это разочаровывает (заставляя вас спросить, почему последний hash_updateвсегда терпит неудачу, ведь все работает нормально hash_update).

Тем не менее, это не значит, что я добавляю {}везде (по моему мнению, смотреть {}везде раздражает). Хотя это может вызывать проблемы, этого никогда не случалось для моих собственных проектов, поскольку мой личный стиль кодирования запрещает условные выражения, {}если они не совпадают (да, я согласен, что мой стиль кодирования нетрадиционен, но мне это нравится, и я использовать стиль кода проекта при участии в других проектах). Это делает следующий код в порядке.

if (something) goto fail;

Но не следующий.

if (something)
    goto fail;
Конрад Боровски
источник
Именно. Только не ставьте (совершенно ненужный) символ новой строки + отступ, и вы полностью обойдете эту проблему, которую все всегда так быстро поднимают.
Александр - Восстановить Монику
4

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

Дрона
источник
4

WRT 6: это безопаснее, потому что удаление нулевого указателя не работает. Так что, если вам случится случайно пройти этот путь дважды, вы не вызовете, чтобы повреждение памяти освободило память, которая либо свободна, либо была выделена для чего-то другого.

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

В большинстве случаев вы можете избежать этого, используя auto_ptrs

Том Таннер
источник
6
Если вам случится пройти этот путь дважды, вы получите ошибку программирования. Установка указателя на ноль, чтобы сделать эту ошибку менее вредной, не решает основную проблему.
Пит Беккер
1
Согласен, но я видел это рекомендованным ранее, и я верю, что это в некоторых профессиональных стандартах программирования. Я комментировал больше о том, почему профессор плаката придумал это, а не когда это было хорошо
Том Таннер
2
Следуя тому, что сказал Пит Беккер: это не решает основную проблему, но может маскировать ее. (В некоторых случаях вы можете установить указатель на NULLпосле удаления. Если NULLэто правильное значение, которое указатель должен иметь при таких обстоятельствах; например, указатель указывает на кэшированное значение и NULLуказывает на недопустимый кеш. Но когда вы видите, что кто-то настраивает указатель NULLна последнюю строку в деструкторе, вы задаетесь вопросом, знает ли он C ++.)
Джеймс Канз
4

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

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

выглядит загроможденным для меня. Он разделяет цикл for и оператор if на отдельные действия, когда на самом деле ваше намерение - это одно действие: подсчитать все целые числа, делимые на 2. В более выразительном языке это можно записать примерно так:

j = [1..100].filter(_%2 == 0).Count

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

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}
MikeFHay
источник
7
Мне нравится, как всем удается игнорировать for (int i = 0; i < 100; i += 2);, ради продолжения спора об отступах ;-) Возможно, у нас может быть целый отдельный булфрайт, как «лучше» выразить логику «для каждого iв определенном диапазоне с определенным свойством» в C ++ без цикла, используя некую кошмарную комбинацию стандартных алгоритмов filter_iteratorи / или counting_iterator.
Стив Джессоп
1
Кроме того, если бы у нас было это, то мы могли бы не согласиться с тем, как сделать отступ в результирующем массивном единственном утверждении.
Стив Джессоп
2
@Steve, это всего лишь пример. Существует множество законных способов использования шаблона. Очевидно, что если вы хотите посчитать числа от 1 до 100, которые делятся на 2, все, что вам нужно сделать, это 100/2.
MikeFHay
2
Конечно, я знаю, поэтому я абстрагировался от «для каждого iв определенном диапазоне с определенным свойством». Просто на SO обычно люди очень быстро игнорируют реальный вопрос в пользу совершенно другого подхода к приведенному примеру. Но отступы важны , поэтому мы не делаем ;-)
Стив Джессоп
4

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

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong
Черный тигр
источник
5
Второй вариант приведет к ошибке компиляции, потому что elseне связан с if.
Лучиан Григоре
Одна не очень заметная проблема со встроенными функциями заключается в том, что большинство IDE по умолчанию изменяют его на стиль с отступом при использовании своей утилиты автоформатирования.
Хонза Брабек
@Honza: это очень напряженная политическая проблема, все же. Если мы сотрудничаем на базе кода, либо мы должны использовать один и тот же стиль отступа до каждой детали, либо мы оба согласны не выполнять автоформатирование существующего кода «просто потому, что». Если первое, то согласованный стиль все еще может включать это, но вам придется либо сконфигурировать вашу IDE для соблюдения этого, либо не использовать автоформат. Согласиться с тем, что общий формат «к чему бы ни относились мои автоформаты IDE», - это очень хорошо, если мы все будем использовать одну и ту же IDE навсегда, иначе не так уж хорошо.
Стив Джессоп
3

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

Но для программистов первый более понятен, легко читается и менее подвержен ошибкам.

PP
источник
2
В {любом случае, кроме открытия по собственной линии.
Роб
3

Еще один пример добавления фигурных скобок. Однажды я искал ошибку и нашел такой код:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

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

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

В результате SetAnotherVariableUnconditionnaly выполняется, когда SomeConditionIsMet (), но быстрый парень этого не заметил, потому что все строки почти одинакового размера, и даже если условие возврата имеет отступ по вертикали, это не так заметно.

Если условный возврат отформатирован так:

if (!SomeConditionIsMet())
{
    return;
}

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

Artemix
источник
Если ваш быстрый кодер не может потрудиться обнаружить синтаксически выделенный returnоператор в теле функции перед добавлением чего-либо в нее, вы не должны позволять быстрому кодеру приближаться к вашему коду. Вы не остановите такого парня от троллинга вокруг вашего кода, включая фигурные скобки.
cmaster - восстановить монику
@cmaster Он больше не работает с нами. В любом случае, подсветка синтаксиса хороша, но помните, что есть люди, которые не видят ясно (я видел даже сообщение от слепого программиста в прошлом году).
Артемикс
2

Я считаю, что первое ясно, а потом второе. Это дает ощущение закрытия инструкций, с небольшим кодом хорошо, когда код становится сложным, очень {...}помогает, даже если это endifилиbegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;
RollRoll
источник
1

Лучше всего установить указатель на NULL, когда вы закончите с ним.

Вот пример почему:

Класс А делает следующее:

  1. Выделяет блок памяти
  2. Затем, через некоторое время, он удаляет этот блок памяти, но не устанавливает указатель в NULL.

Класс B делает следующее

  1. Выделяет память (и в этом случае ему предоставляется тот же блок памяти, который был удален классом А.)

На этом этапе и класс A, и класс B имеют указатели, указывающие на один и тот же блок памяти, поскольку для класса A этот блок памяти не существует, поскольку он завершен с ним.

Рассмотрим следующую проблему:

Что если в классе A произошла логическая ошибка, из-за которой он записал в память, которая теперь принадлежит классу B?

В этом конкретном случае вы не получите ошибку исключения неправильного доступа, поскольку адрес памяти допустим, в то время как класс A теперь эффективно портит данные класса B.

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

Если бы вы установили указатель удаленной памяти в NULL, вы бы получили ошибку исключения, как только любые логические ошибки в классе A попытались записать в указатель NULL.

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

Также: если вы собираетесь проголосовать против, пожалуйста, объясните.

Джон
источник
2
Если произошла логическая ошибка, ее следует исправить, а не маскировать.
uınbɐɥs
@ Barmar, OP говорит ... 6. Установите Pointer на NULL после удаления - Двойная защита удаления // неплохо. Некоторые люди ответили, что не установили его в Null, и я говорю, почему он должен быть установлен в NULL, какая часть из 6. Мой ответ на установку NULL не вписывается в 6?
Джон
1
@Shaquin, а как вы предлагаете найти эти логические ошибки в первую очередь? После того, как вы установите переменную указателя в NULL, после удаления памяти. Любая попытка сослаться на указатель NULL приведет к сбою в отладчике в строке, где была сделана ваша незаконная попытка. Вы можете отследить и увидеть, где была логическая ошибка, и устранить проблему. Если вы не установите переменную указателя в NULL после того, как удалили память, ваша незаконная попытка записи этой удаленной памяти из-за логических ошибок UNAWARE может увенчаться успехом и, следовательно, не потерпит крах в этой точке. Это не маскировка.
Джон
1

Всегда иметь фигурные скобки - очень простое и надежное правило. Однако код может выглядеть не элегантным, когда есть много скобок. Если правила позволяют опускать фигурные скобки, то должны быть более подробные правила стиля и более сложные инструменты. В противном случае это может легко привести к хаотическому и запутанному (не элегантному) коду. Поэтому поиск единого правила стиля отдельно от остальных руководств по стилю и используемых инструментов, скорее всего, бесполезен. Я просто приведу некоторые важные детали об этом правиле № 3, которые даже не упоминались в других ответах.

Первая интересная деталь заключается в том, что большинство сторонников этого правила соглашаются нарушать его в случае else. Другими словами они не требуют в обзоре такой код:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

Вместо этого, если они видят это, они могут даже предложить написать это так:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Это технически является нарушением этого правила, поскольку между elseи if. Такая двойственность правила возникает, когда пытаться автоматически применить его к базе кода с помощью бессмысленного инструмента. Действительно, зачем спорить, просто дайте инструменту применить стиль автоматически.

Вторая деталь (которую также часто забывают сторонники этого правила), это то, что ошибки, которые могут произойти, никогда не происходят только из-за нарушений этого правила № 3. На самом деле это почти всегда связано с нарушениями правила № 1 (с которым никто не спорит). Опять же, с точки зрения автоматических инструментов, нетрудно создать инструмент, который немедленно жалуется, когда нарушается правило № 1, и поэтому большинство ошибок могут быть своевременно обнаружены.

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

Öö Tiib
источник
1

Я должен признать, что не всегда использовать {}для одной строки, но это хорошая практика.

  • Допустим, вы пишете код без скобок, который выглядит следующим образом:

    for (int i = 0; i <100; ++ i) для (int j = 0; j <100; ++ j) DoSingleStuff ();

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

  • Освобождение памяти происходит быстрее. Допустим, у вас есть большие области видимости и вы создаете большие массивы внутри (без newтак, чтобы они были в стеке). Эти массивы удаляются из памяти сразу после выхода из области видимости. Но возможно, что вы используете этот массив в одном месте, и он какое-то время будет находиться в стеке и будет своего рода мусором. Поскольку стек имеет ограниченный и довольно маленький размер, его размер может превышать. Так что в некоторых случаях лучше написать, {}чтобы предотвратить это. ПРИМЕЧАНИЕ, это не для одной строки, а для таких ситуаций:

    if (...) {// SomeStuff ... {// у нас нет if, while и т. д. // SomeOtherStuff} // SomeMoreStuff}

  • Третий способ его использования аналогичен второму. Это просто не для того, чтобы сделать стек чище, а чтобы открыть некоторые функции. Если вы используете mutexдлинные функции, как правило, лучше блокировать и разблокировать непосредственно перед доступом к данным и сразу после окончания чтения / записи. Обратите внимание, что этот способ используется, если у вас есть свои classили structс constructorи и destructorдля блокировки памяти.

  • Что больше

    if (...) if (...) SomeStuff (); еще SomeOtherStuff (); // переходит ко второму if, но аллигент показывает, что он на первом ...

В целом, я не могу сказать, каков наилучший способ всегда использовать {}одну строку, но в этом нет ничего плохого.

ВАЖНОЕ РЕДАКТИРОВАНИЕ Если вы пишете кодовые скобки кода для одной строки, это ничего не делает, но если ваш код будет интерпретирован, это очень сильно замедляет код. Очень немного

ST3
источник
1

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

if (condition)
  statement;

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

if (condition)
{
  statement;
  statement;
}

тогда будет визуально очевидно, какие ifоператоры контролируют одну строку, а какие контролируют несколько строк. Если, однако, многострочные ifоператоры записываются как:

if (condition) {
  statement;
  statement;
}

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

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

if (condition) statement;

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

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

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

Supercat
источник