Когда я только начинал программировать – а это был 1990-й год – особых заморочек по поводу оформления кода не было. Да и смысла это не имело, поскольку сначала был какой-то Basic на БК1001, затем Turbo Pascal 3.0 на Robotron 1715. Насколько я помню, Basic сам все приводил к верхнему регистру. А на Robotron-е, кажется из-за шаманства с добавлением русских символов в кодовую таблицу, строчных латинских букв не было вообще. Поэтому первые программы писались только в UPPERCASE и других вариантов не было вообще.
Чуть позже с Robotron-ов пересел на IBM PC, на Turbo Pascal 5.0. И хоть Pascal не был чувствителен к регистру, программы хотелось оформлять красиво (т.е. так как в книжках и фирменных Borland-овских примерах), поэтому как-то само собой произошел переход к CamelCase. Деталей уже не помню, но вряд ли я писал If, Begin и End. Скорее if, begin и end. А вот функции и переменные точно писал в CamelCase. Даже одно время стандартные функции писал так WriteLn, ReadLn. Но не долго, намного проще было по старинке – writeln, readln.
Там же, на IBM PC к концу первого курса (а это была середина 1991) довелось познакомиться с языком C. После привычного, быстрого и безопасного Pascal язык C казался чем-то жутко сложным, да и изрядно тормознутым (в смысле скорости компиляции и линковки). Да и принятый в нем стиль lower_case вызывал двойственные чувства. Но C – это было намного круче Pascal (просто круче, без подробностей), поэтому изучал я его старательно.
В итоге, когда в самом начале второго курса я написал свою самую большую программу на Pascal (объемом в несколько тысяч строк), то я использовал уже lower_case. Т.е. в Pascal-евской программе все структуры, функции и переменные именовались строчными буквами, а разделителями в именах были подчеркивания. И таким стилем я пользовался затем около года, программируя уже на C, ассемблере x86 и потихоньку изучая C++.
Но где-то в 1992-м мне в руки попал двухтомник по программированию под Windows 3.0 за авторством А.В.Фролова и А.Г. Фролова. Оттуда я узнал по венгерскую нотацию :)
Венгерская нотация произвела на меня очень сильное впечатление. На тот момент она казалась очень логичной, удобной и полезной. Плюс к тому, практически все, что тогда касалось программирования под Windows, использовало именно венгерскую нотацию. Поэтому естественным образом я стал использовать венгерскую нотацию и делал это очень долго. Года где-то до 1998, если не позже.
Основными причинами, которые побудили меня отказаться от венгерки, были:
- неудобство сопровождения кода. Со временем типы переменных менялись. Если раньше nPercentage была целочисленной, то со временем ее требовалось сделать вещественной и приходилось переименовывать ее в fPercentage. Соответственно, изменяя все места ее использования. Сейчас, при наличии IDE с мощными средствами рефакторинга в это сложно поверить, но тогда переименовать какую-то сущность в большом количестве исходников было не просто :)
- маразм, до которого стали доходить в формировании префиксов. Когда венгерка ограничивалась префиксами i, n, f, l, p, pc, sz и еще парочкой таких же простых сочетаний, это было вполне удобно. Но когда приходилось писать что-то вроде ppcsz, pppi и пр., закрадывались сомнения в разумности происходящего :) Апогеем же стала как-то попавшая ко мне в руки книжка по Visual Basic-у, в которой перечислению “стандартных” префиксов была посвящена здоровенная таблица на десяток страниц.
Были и другие мелкие и не очень недостатки, но они свойственны не столько венгерской нотации, сколько стилю CamelCase вообще. А к стилю CamelCase я вернулся одновременно с переходом на венгерскую нотацию. И использовал его намного дольше, где-то до 2001 года.
Тут нужно чуть подробнее остановиться на особенностях CamelCase применительно к объектно-ориентированным языкам. В процедурных языках, вроде C или Pascal, все довольно просто. Но в том же C++ добавляются еще и классы, их атрибуты и методы. Как-то само собой получалось, что нужно уметь их выделять визуально среди остальных сущностей. Поэтому были разные варианты CamelCase, в которых существовали более-менее серьезные различия.
Нужно сказать, что упомянутая мной книга Фроловых о Windows 3.0 кроме приобщения к венгерской нотации, донесла до меня (и моих коллег) очень важную вещь – в отдельно взятом коллективе все должны использовать один и тот же стиль оформления кода. До этого я об этом даже не задумывался, а затем стал твердым приверженцем этой точки зрения, хотя и без фанатизма.
Так вот, для того, чтобы поддерживать единообразие кода в тех коллективах, где я работал в 1994-1996 годах, был выработан некий вариант стиля CamelCase, которым я пользовался до 2001 (и который, возможно, живет там до сих пор). Основными его чертами были:
- префикс T для имен типов. Т.е. TFile, TByteStream и т.д.;
- префикс m для имен атрибутов. Т.е. TFile::mHandle, TByteStream::mSize, TByteStream::mCapacity и т.д.;
- именование методов со строчной буквы: TFile::open(), TByteStream::writeBlock() и т.д.;
- именование свободных функций с прописной буквы: OpenFile, CreateByteStream и т.д.
Уже не помню, почему нам казалось, что имена методов и имена свободных функций в обязательном порядке должны различаться. Вероятно, где-то это имело смысл, где-то нет. В любом случае после перехода на lower_case я от такого деления отказался.
А вот специальное выделение имен типов и имен атрибутов для меня имело и имеет практический смысл. Причина, нужно сказать, банальная – лень. Мне тупо лень выдумывать разные имена. Так, пусть у меня есть тип file и мне нужно в какой-то маленькой функции создать переменную этого типа. Если имена типов ничем не отличаются от других сущностей, то я уже не могу написать просто:
file file(…);
Поскольку компилятор даст по рукам. А вот так запросто:
TFile file(…);
Еще больше эта лень дает о себе знать с именами атрибутов. Ведь если у класса есть инициализирующий конструктор (а часто он есть), то имена аргументов конструктора часто совпадают с именами атрибутов. Поэтому, если у атрибутов нет никаких префиксов (суффиксов), то вот так просто уже не напишешь:
TPoint(int x, int y) : x(x), y(y) {}
нужно придумывать какие-то новые имена для параметров конструктора. Причем чем проще ситуация (как в примере с TPoint) тем сложнее выдумать что-то отличное от x и y. А вот с префиксами для атрибутов нет никаких заморочек:
TPoint(int x, int y) : m_x(x), m_y(y) {}
С атрибутами есть еще одна штука. В теле метода очень хочется видеть где ты изменяешь атрибут, а где локальную переменную. И префиксы для атрибутов в этом так же помогают. Я знаю, что многие для этих целей используют конструкции вида this->x или this.x, но я ленивый. Мне проще набрать m_x, чем this->x ;) Кроме того, если придется запрограммировать какое-нибудь математическое выражение, то с именами m_x, m_y оно еще будет читаться, а вот с this->x и this->y уже вряд ли.
В общем, CamelCase я использовал до 2001. Но в тот год решительно и бесповоротно (надеюсь) перешел на lower_case. Причина банальна – работая с кодом в lower_case у меня глаза устают намного меньше, чем с CamelCase. Видимо, начал сказываться возраст и многие часы, проведенные за не самыми хорошими мониторами. Как бы то ни было, в один прекрасный момент я понял, что illegal_indirection пишется и читается намного удобнее, чем IllegalIndirection. Причем вне зависимости от шрифта.
Поскольку я стал пользоваться lower_case, то претерпели изменения четыре упомянутых выше принципа:
- типы имеют суффикс _t вместо префикса T;
- атрибуты имеют префикс m_ вместо m (поскольку если раньше mX читалось нормально, то сейчас mx уже совсем не то, в отличие от m_x);
- имена методов и свободных функций записываются строчными буквами и не отличаются.
Нужно сказать, что поначалу я еще пытался делать разные правила для разных ситуаций. Например, пытался применять подчеркивание в конце имени свободной функции (т.е. create_file_ вместо create_file), но это оказалась нежизнеспособная идея. Так же использовал разные префиксы для разных сущностей: m_ для атрибутов, g_ для глобальных переменных, c_ для констант, e_ для перечислений. Но со временем выжили только m_ для атрибутов и g_ для глобальных переменных (да и то не всегда).
Вообще говоря, идеальных нотаций (по крайней мере для C++) я пока не встречал. Везде что-нибудь, как-нибудь, но мешает. Поэтому я стал намного более спокойно относиться к разным вариациям. Скажем, обзывать имена констант, элементов перечисления и параметров шаблонов прописными буквами. Ничего страшного в мелких экспериментах со стилями я не вижу. Мы с возрастом меняемся, опыта прибывает, интересные идеи у других подсматриваем. Все течет, все изменяется, наши привычки, наши вкусы и наш код в том числе.
Главное при этом выдерживать единый стиль. Можно легко разобраться в коде, где человек именует классы, например, SMTP_session_t или SMPP_parser_t (вместо более привычного тебе smtp_session_t или smpp_parser_t). И придерживается этого стиля во всем своем коде. Но вот когда в начале заголовочного файла дается имя SMPP_paser_t, а следом за ним идет smpp_parser_state_t – вот это уже плохо. Т.к. заставляет искать разумную причину разных подходов к именам. Даже если ее нет :)
Напоследок еще несколько лирических отступлений. Самые удобные стандартные стили кодирования я видел в Eiffel и Ruby. Но, если говорить об Eiffel, то там язык не столь богат на возможности, как C++ – в нем есть только классы, методы и атрибуты. А вот в Ruby используется зашитая в синтаксис языка система префиксов для глобальных переменных, атрибутов классов и статических атрибутов классов (если пользоваться терминологией С++). Что мне представляется пока и разумным, и удобным. Понятное дело, что поскольку мне не нравится Java, то мне не нравится и ее стиль оформления кода. Ну а C# – это та же Java, но немного другая ;)
Если же говорить об отступах и фигурных скобках, то я перепробовал разные их сочетания:
if(...)
{
}if(...) {
}
if(...)
{
}
Все они работают. Я могу свободно читать и писать код в любом из этих стилей. Но для меня самым удобным оказался последний вариант. Поскольку лично мне вот такой код разбирать много проще:
if(...)
{
}
else
{
}
чем вот такой:
if(...)
{
}
else
{
}
Но, полагаю, это дело личных предпочтений.
А вот чего я не люблю, так это экономии на пробелах и переносах строк. И никогда не мог понять, как люди умудряются писать и читать вот такой код:
if(0==Hf&&0==Mf)return 24;
если можно записать его вот так:
if( 0 == Hf && 0 == Mf )
return 24;
В заключении пару ссылок на самого себя:
Давно хотел спросить - в чем вы код для блога оформляете? В этом посте не так, но у вас обычно код на синем фоне
ОтветитьУдалитьКстати, вот так вполне напишешь:
ОтветитьУдалитьstruct TPoint
{
int x, y;
TPoint(int x, int y) : x(x), y(y) {}
}
@Eugeniy
ОтветитьУдалитьда, напишешь
я писал об этом чуть раньше, и теперь шаг 2 -- проблемы
проблемы могут быть, но префикс m_ от них не спасет
предположим, что this->y не может быть больше х, и тогда хочется написать
TPoint(int x, int y) : x(x), y(std::min(x,y)) {}
а затем после эволюции:
TPoint(int x, int y) : x(x), y(std::min(x,y)) { call_global_f(x,y); } <----- какой y???
решение -- не допускать таких совпадений имен, и писать либо
TPoint(int x, int wishful_y) : x(x), y(std::min(x,wishful_y)) {}
либо иногда еще лучше
TPoint(int x, int wishful_y) : x(x), cropped_y(std::min(x,wishful_y)) {}
то есть длинные, говорящие префиксы вместо m_
@Евгений Охотников
ОтветитьУдалитьнасчет CamelCase vs. camel_case_t -- по-моему, хорошего решения просто нет, все немножко плохи
есть еще навроде ады Camel_Case -- вроде и хорошо, но выглядит непривычно, что тоже плохо
нет в мире щастья, короче :-)
добавлю насчет Camel_Case: моя нелюбовь к большим буквам доходит до того, что я и на русском пишу без них, как многие могли заметить
ОтветитьУдалитьа различать типы и значения как-то надо... вот и крутись :-)
уточню свою мысль
ОтветитьУдалитьрегистр символов и суффикс _t можно считать не очень важными вопросами эстетики и стиля
но вот наличие двух разных переменных x и m_x с одним и тем же значением это уже существенно плохо
более того, тревожно даже наличие двух переменных right и range.right -- с чего и начался этот разговор (однако, наличие двух переменных range и corrected_range одного типа и следовательно с одинаковыми полями, очевидно, нормально)
в случае
struct S {
const double m_start;
const double m_end;
S(double middle, double length): m_start(middle-length/2), m_end(middle+length/2) {}
}
префикс m_ всего лишь это вопрос стиля, а вот в случае x и m_x это уже существенно хуже
одна сущность должна называться одинаково, а разные сущности должны называться достаточно (существенно?) по-разному
префикс m_ не дает однозначного ответа на вопрос -- имеют ли право переменные x и m_x принимать разные значения, или это всего лишь разные обозначения одного и того же?
2 имя
ОтветитьУдалить>>> а затем после эволюции:
>>> TPoint(int x, int y) : x(x), y(std::min(x,y)) { call_global_f(x,y); } <----- какой y???
Эклипс имеет семантическую подсветку С++ - члены, аргументы, локальные и глобальные подсвечиваются по разному :)