четверг, 26 августа 2010 г.

Допустимая публичность

В C++ традиционным считается использование public методов в начале описания. И сразу слайды:
class foo {
public:
        foo();
        void doSomething();
private:
        ...
};
И это логично. При чтении кода первое, что мы видим, это доступные нам рычаги для воздействия на класс, его интерфейс. Для тех, кто дочитал до private, становятся известны некоторые подробности реализации. Но речь не о том...

В java объявлений, как таковых нету. В нем объявление и реализация существуют неразрывно. И там принято переменные описывать в начале класса.

В принципе, что в c++, что в java, нет никаких ограничений на порядок следования описаний в классе. Но то, что принято в java - выглядит логично. После описания переменных следуют реализации, которые эти переменные используют.

В том же C++ никто не отменял предварительные описания классов, отдельных функций и переменных. Именно поэтому код выглядит странно:
class bad {
public:
        void action() {
                badness = 100;
        }
        ...
Мы сразу пытаемся найти эту переменную выше по коду. Не пытайтесь найти ее там, это невозможно. Эта переменная находится на несколько сотен строк ниже, перед закрывающей класс скобкой. Мозг протестует, но в c++ это допустимо.

Я вообще с некоторых пор перестал пользоваться инлайн объявлениями, за исключением темплейтов.

Некоторые утверждают, - что дескать инлайн объявления инлайнятся, что делает программу быстрее. А вы уверены? Утверждения о скорости работы необходимо подтверждать цифрами. 80% преждевременной оптимизации лишне. Стоило ли ради этого мнимого ускорения жертвовать удобочитаемостью программы? Ведь освобождая интерфейс класса от ненужных подробностей мы облегчаем его понимание и использование.

Кроме того если возникнет необходимость отладки - лично мне мешаются инлайн функции. Я бы предпочел чтобы каждая функция в исходном тексте оставалась такой же отдельной и в исполняемом модуле. Чтобы по одному содержимому стека проблема прояснялась. Но gcc последнее время стал слишком умным. даже при -O0 -ggdb3 он все равно инлайнит почем зря.

Кроме того, даже самые маленькие методы, такие как int getX() const { return x; } со временем могут подрасти. Что приводит к классам на 500 строк описания, но почему то без cpp файла.

Поэтому я почти всю реализацию описываю отдельно от описания класса. Пусть описание будет чистым. И содержит только то, что невозможно убрать.

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

9 коммент.:

Евгений Охотников комментирует...

Долго соображал, почему я не сталкивался с описанной проблемой. Потом понял -- у меня же префиксы для атрибутов используются. Т.е. вместо badness=100 у меня было бы m_badness=100. Поэтому сразу понятно, где искать определение m_badness.

Андрей Валяев комментирует...

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

Хорошо, если автор кода всетаки придерживался некоторых правил и есть вероятность, что описание внизу... но оно с таким же успехом может быть зарыто между методами. :)

По поводу префиксов - Макконел рекоммендует использовать префиксы, но Мартин сотоварищами рекоммендуют писать классы попроще, и не засорять код всякими префиксами... Так что я что-то даже не знаю как писать. :)

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

Жаль только что таких классов в жизни не встречается. :)

Андрей Валяев комментирует...

А на C++ - если писать описания без кода - то они редко бывают большими. Так что все переменные класса должны быть хорошо видны.

Просто код засоряет описание и усложняет чтение. В любом случае, не зависимо от наличия или отсутствия префиксов.

Евгений Охотников комментирует...

>А на C++ - если писать описания без кода - то они редко бывают большими. Так что все переменные класса должны быть хорошо видны.

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

Мартин (Фаулер, я подозреваю), в основном на Java/C# специализирется. Там вообще многие пишут в стиле this.badness, поэтому им и префикс не нужен -- эту роль "this." играет.

Андрей Валяев комментирует...

Если в дело вступает наследование, и использование переменных базового класса считается допустимым - то тогда вопрос поиска m_badness тоже значительно усложняется. :)

А шаблоны... Не так уж активно в корпоративной среде используются различные шаблонные извращения. принцип KISS рулит.

Касательно префиксов - кто-то рекомендует и локальные переменные сопровождать префиксами l_badness.

Я тоже обычно пишу m_... но последнее время что-то задумался. :)

С чем можно спутать badness?

Можно спутать с локальной переменной. Но если твоя функция не 3000 строк а приемлемые 20-30, эта проблема не столь остра.

Можно спутать с глобальной переменной. Но глобальные переменные вообще подвергаются всеобщему гонению...

Получается что вариантов особо нету :) лаконичные функции и переменные класса без префиксов будут интуитивно понятны. :)

Анонимный комментирует...

Жаль, что нельзя так:

class bla {
public:
void foo();
private:
int mem;
public:
void foo() { cout << mem << endl; }
};

PS какой-же этот blogger.com убогий в плане доступных тегов. Ни html, ни bbcode :(

Евгений Охотников комментирует...

Так ведь можно чуть иначе:

class bla {
public:
void foo();
private:
int mem;
};
inline void bla::foo() { cout << mem << endl; }

Андрей Валяев комментирует...

Вот слово inline мне особенно не нравится. :) ИМХО зло.

И gcc ее, очень грамотно, практически игнорирует.

Код на C++ вообще достаточно непредсказуем, глядя на cout << mem << endl; Я не знаю в какое количество кода это выльется.

Может быть в пару вызовов, что для инлайн нормально. А может быть в немерянную портянку темплейтного кода...

Поэтому ИМХО лучше описать отдельно.

Анонимный комментирует...

Насчёт определения снаружи класса - да, верно, это я затупил.

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

Возвращаясь к теме - не считаю злом в хидерах определения геттеров (и сеттеры, но сеттеры плохи в целом) и тривиальных *структоров. Ввод-вывод в реальных проектах в хидере не пишу никогда - на фоне стоимости IO оверхед на cal/ret незначителен, а переписывается форматный вывод часто -> в хидеры бы это затормозило сборку.