Pull to refresh

Comments 195

> C++. 10 лет практики.

Мне очень лень было придумывать коммент в духе тонкого троллинга, поэтому я просто напишу «lisp».
Автор комментария, очевидно, считает, что вместо того чтобы 10 лет писать на С++ нужно было освоить Common Lisp
Скандалы, интриги, расследования.
Последние бастионы лисперов пали. Представители NASA признались, что в марсоходах код на C и C++ (да, в самих марсоходах и да, C++ явным образом назван). Даже в ответе на вопрос о DSL-ах не было никакого упоминания lisp.
channel9.msdn.com/blogs/nicfill/channel-9-live-at-pdc09-dr-jeff-norris-nasa (перемотать на 23:30)
Рекомендую прослушать все, чтоб представить какие проблемы решаются
UFO landed and left these words here
На лисп больше ведутся) Вон сколько прикармливателей собрал)
Мне кажется, если Вам лень что-то придумывать, то вообще ничего не придумывайте, чем пишите всякую чушь.
Ваша статья пока похожа на поток сознания нежели на какое-то объяснение или руководство к действию.
Возможно следовало бы сначала немного структурировать все?

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

тоже немного оффтопа: хорошо бы хабру заиметь понятие «цикл статей»…
Мне кажется, или вы говорите скорее не о Си++, а об объектно-ориентированном подходе в целом? А то, что вы говорите о разделении программы на подзадачи справедливо для абсолютно любого языка.
Кроме того, если учесть тот факт, что Си++ — язык общего назначения, поддерживающий (если это корректное слово в данном случае) множество парадигм программирования, то всё вышесказанное про различие структуры Си++ и Си-программ вообще не имеет смысла, ибо единственное что тут превносит Си++ — ООП, которое, естественно, меняет общую структуру. Но никто это ООП не заставляет применять.
Забавно. Ваши рассуждения, если провести аналогию, выглядят как раз программой на C++ в том стиле C, который автор в статье категорически советует избегать. Поясню: написать и на C++ можно как угодно деструктурированно, но ваши тексты станут бесполезны, стоит измениться условиям задачи. И аналогично, опытный программист избегает подобных рассуждений.
Хотя сухой остаток в пересчете на полезную информацию пока довольно невелик, все равно спасибо, было интересно почитать.

Только я не совсем понял: вы вообще против свойств/автосвойств?
Моей задачей было написать цикл статей, который описывает весь цикл разработки. Как я и написал в начале, первая статья рассчитана скорее на начинающих. Предполагаю что Вы, просто, более опытный программист, все эти вещи давно знаете. Разумеется, это лишь моё мнение.
Был бы очень благодарен за конкретные идеи как упорядочить данную статью.
Мне кажется, или вот здесь есть противоречие:
1. Спрашивается, сколько внутренней информации о Бункере я открыл другим классам? Отвечаю: вообще ничего. Понимаете? Все члены класса — приватные. Никто извне не должен знать что у класса внутри.
2. При этом, он сам выводит себя в интерфейс, сам обрабатывает нажатия на свои контролы, сам выполняет всю работу, которая требует знания о том, как он устроен.

Для того, чтобы спрятать реализацию бункера, были открыты детали реализации UI? Если поведение бункера завязано на «обработку нажания на свои контролы» и «отрисовку себя в интерфейс», то каким образом «малой кровью» получилось сделать удаленное управление, у которого, очевидно, никаких «контролов, на которые можно нажимать» и никакого «интерфейса, куда можно себя рисовать» нет?

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

Для начала, небольшое введение. ООП, в отличие от того, что считают некоторые, это не просто объекты со свойствами. В этом смысле оно мало чем отличается от С-структур. Главная мощь ООП/С++ раскрывается в слабом связывании классов. Это значительно снижает стоимость отладки и поддержки для больших программ.

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

Ещё раз: вот есть Бункер, он управляется (или нет) рядом гуишных контролов. Эти контролы несут внутреннее представление Бункера, они, по всей логике, механизмы Бункера по общению с человеком. Вывод: они должны быть частью бункера. Это будет правильно с точки зрения семантики, правильно с точки зрения инкапсуляции:
class Silo
{
private:
ComboBox type;
Button manualMeasure;
};

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

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

В противном случае, Вам приходится писать дополнительные «связующие» функции-члены класса, которые «привязывают» его к внешним контролам, обновляют его из внешних контролов и наоборот. Это не смертельно, но несколько увеличивает связующий интерфейс между ГУИ и самим классом:
//хороший случай, когда фреймворк позволяет классу хранить контролы у себя
class Silo
{
public:
void InitializeGUI(Window *);
private:
Button manualMeasure;
};

//когда фреймворк этого не позволяет
class Silo
{
public:
void InitializeGUI(Button *manualMeasure);
private:
Button *manualMeasure;
};


Теперь что касается MVC. Очевидно, что взаимосвязь между M, V и C слишком сильна, так как переносит всю внутреннюю кухню в открытый интерфейс. По сути, это означает, что три компонента являются друг для друга полностью открытыми. Соответственно, пользоваться ей, разделяя M, V и C по разным классам, я не рекомендую. Эти три компонента очень неплохо разделяют внутренний функционал класса, будучи оформленными как три блока членов и функций-членов класса.
Мой опыт показал: проще реализовать всю кухню внутри одного класса, чем искусственно делить на некие абстракции, которые к тому же слишком сильно связаны, что грозит адскими интерфейсами, которые тупо «гоняют» все логически связанные внутренности (которые по идее никто не должен видеть) между какими-то левыми классами. Зачем вводить внешнее управление над контролами Бункера, если он, по идее, сам с этим отлично справится? Это не только уменьшит код, но и сделает его стабильнее и более поддерживаемым, в конечном итоге.

В итоге, для реализации бункера НЕ были использованы детали ГУИ, потому что все контролы принадлежат самому бункеру (мой фреймворк это позволяет). Для инициализации мне достаточно указателя на окно. Соответственно, когда потребовалось удалённое управление, интерфейс не изменился. Просто в случае удалённых бункеров, я создаю другие контролы (и много чего другого, с ГУИ не связанного).
Это подтверждается, если мы подумаем о времени жизни контрола: она должна совпадать с временем жизни родительского объекта, а не класса.
-->
Это подтверждается, если мы подумаем о времени жизни контрола: она должна совпадать с временем жизни родительского объекта, а не объекта окна.
Немного не понял вот какую вещь.
Допустим, есть класс с целым параметром: class My { int mNumber; };
Я хочу управлять им через комбо-бокс. Пожалуй, я бы написал (нехорошо, понимаю) set функцию и вызывал бы её в OnComboBoxUpdate(). А вы предлагаете напрямую сделать так: class My { ComboBox* mNumber; };?

Или речь идёт лишь о том, чтобы поместить комбобокс в класс My, но всё остальное остаётся прежним?..
Что конкретно предлагаю в вашем случае:
1) Отказаться от Сет() функции.
2) КомбоБокс перенести внутрь класса, если позволяет фреймворк. Если не позволяет, т.е. КомбоБокс автоматически является членом класса Окна, связать ваш класс с указателем на КомбоБокс так, как Вы это показали.
3) Вся работа с КомбоБокс — внутри класса. В частности: установка начального значения контрола, а также привязывание колбэков КомбоБокса к функциям-членам класса, чтобы класс внутри себя обрабатывал нажатия пользователя.
Благодаря такому подходу, вся работа с конкретным контролом будет внутри класса. Там, где располагаются внутренние переменные, а значит нет необходимости городить лишние открытые интерфейсы для обмена данными.
Меня в этом подходе смущает следующее:
(кратко) противоречие MVC.
(полно) «завязка» класса на GUI. Его сложнее сериализовать, он «тянет» за собой весь гуи фреймворк (становится тяжёлым), его придётся переписывать, если поменялся гуи фреймворк, да и непонятно что делать, если гуи пишет один человек, а класс разрабатывает другой.

В общем, мне это кажется шагом назад, хотя, конечно, сеттер уйдёт. Короче говоря, терзают смутные сомнения… :)
Спасибо за конструктивные комментарии, они мотивируют.

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

Теперь что касается второго замечания. Очень хорошо понимаю ваши сомнения.
В начале я разделял «ядро» и «визуализацию». Потом я стал замечать: вот я правлю класс, вот у меня слегка сменились закрытые члены класса. Значит следом меняется объявление интерфейсов для «визуализации» и, зачастую, вместе с ним и объявления функций-членов «ядра». Слишком много лишней работы уходило на каждое именение внутренних мехиназмов класса. Неприятно, но, в общем-то ещё терпимо.

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

Всё равно тот же самый функционал (ядро + визуализация) в программе есть. Только он разнесён по классам. Фактически, то, что я между ними «гоняю» туда-сюда, должно быть внутренним содержимым. Чёрт с ним, попробую сделать в одном классе. Пусть он будет уметь всё сам. И что? Да, я меняю кое-что в ходе программы, но огромных интерфейсов, где я обновляю состояние объекта по 25 контролам (либо одной функцией, либо кучей других — неважно) — их больше нет, понимаете?
Одно из ключевых преимуществ MVC в том, что V и C можно при желании безболезненно отрезать и использовать только M. Или наоборот — подменить одно M на другое без необходимости что-то менять в V и C. Как в вашем «этом, скорее, логическом разделении на три группы. Например, функций-членов.» провернуть вышеописанное?
извините, но это бред. Не использовать общепринятые best practices, мотивируя это простотой — это деление на ноль.
Не понимаю, в каком месте ваших мыслей идёт выброс исключения.)
Я предлагаю некий подход. Ссылаюсь на свой личный опыт, а также на автора книги, которого очень уважаю.
Если для вас достаточно мнение неких других уважаемых личностей, и вы, не попробовав лично, считаете подход нерабочим — просто не пользуйтесь им.

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

Ошибаетесь — мне приходится иметь дело с последствиями такого подхода («всё в одном»). И мне это, между прочим, совсем не нравится.
Готов обсудить на реальных примерах из вашей практики.
Вы мне предлагаете выложить код, который принадлежит моей компании? :) Может, лучше Вы выложите свой код — заодно покажете, насколько красиво и просто реализована Ваша система? :)
Не проблема — скопировать код сюда. Вопрос в том, как просеять все эти килобайты, чтобы было понятно. Для примера, пожалуйста:
class Silo : public UserLevel::Notify, public SiloParamsSourceNotify
{
public:
	Silo(SiloListNotify *notify, int role, const int &serverAddr);
	~Silo();
	const Silo &operator= (const Silo &);
	
	void InitRemoteBehaviour();

	//settings window
	void UpdateUI(ArrayCtrl *params, ArrayCtrl *sensorParams, DropList *sensorType);

	double GetPercentage();

	//main window
	EdgedButtonOption * UpdateButton(int x, int y);
	EdgedButtonOption * GetButton();
	ButtonOption      * UpdateSiloDetails(Label *); //возвращает указатель на кнопку "Измерить" для добавления в окно

	bool IsError  ();
	bool IsWarning();
	
	bool UpdateStatus();

	void InitShutdown();
	void WaitFinish();

	virtual void OnUserLevel (int level, const String &user);
	void Xmlize(XmlIO &xml);

	virtual void OnSensorTypeChanged();

private:
	String GetPercentageString();
	String GetVolumeString();
	String GetHeightString();
	String GetMassString();

	One<SiloCore>   core; friend class SiloGroup;
	One<SiloParams> params;

	void ShutdownRemoteBehaviour();

	void ManualSensorMeasure();
	void UpdateNextMeasureTime();
	
	SiloListNotify   *notify;
	
	EdgedButtonOption button;
	ButtonOption      manual;
	
	SiloSensor       *sensor;
	
	bool              shutdown;
	
	void GetDHPic(double h, double *dp, double *hp);
	void GetDHPicBig(double h, double *dp, double *hp);

	int    userLevel;
	String user;
	bool   userLevelChanged;
};
как-то непонятно зачем связывать цех и UI к нему в один класс… эта простота вылезет потом боком…
неужели вы это применяли в геймдеве?
Вы точно читали статью?
Каким именно боком, по-вашему, и откуда вылезет простота?
я 4 года работаю в геймдеве и знаю что привязывать что-то из core-system к UI большой грех, так как UI может поменяться в любое время дня и ночи, более того вы привязались к конкретному типу контрола, что является еще более изменчивым вариантом…
во вторых кора должна уметь жить сама по себе без какого либо вывода, так как тут уже говорили, однажды придется ее запустить в виде консольной утилиты…
Первое. Данный код не имеет никакого отношения к геймдеву. Так что тут немного промахнулись: здесь другие абстракции. Никто не предлагает объекту сцены работать напрямую с джойстиком. Везде своя специфика, свои абстракции.

Второе. Что касается этого кода, то нет вообще ничего ужасного в привязке к конкретному контролу. При желании я этот контрол могу поменять на другой. При этом не изменится ни один внешний интерфейс. Понимаете?
1, данный код имеет отношение к говнокоду в не зависимости от области применения…

2, я рассказал свою точку зрения, и прочитав все коменты понял что она не лишена здравого смысла, но не могу понять вашу точку зрения, то есть вы из-за довольно простой операции, такой как смена UI будете перелопачивать всю кору? то есть фактически переписывать все, где тут здравый смысл?
Если потенциально возможна смена ГУИ — значит надо так строить абстракции, чтобы код не «ломался». Это называется планирование функционала перед проектированием. В данном случае, требуются абстракции для работы с ГУИ. Это ни в коем случае не противоречит тому, о чём я говорил в статье.
Получается сложный класс? Делим функциональность, в класс добавляем объекты-помощники, либо (более осторожно) используем классы подмешивания.
Вот и вся мысль.
Не понимаю, где вы увидели говнокод, где конкретно в данном случае вы разглядели потенциал к полному перелопачиванию чего-либо вообще.
Наоборот, всё что я предлагаю в статье, сводится к изоляции классов, делению функционала до уровня «простых гвоздей».
Посмотрите на мой класс Silo. Сам по себе он прост как гвоздь, это при том, что, он работает в сетях связи, управляет неким автоматическим объектом реального мира, выводит свои настройки в два интерфейса, синхронизирует себя двумя способами по сети, работает как веб-сервер и веб-клиент в зависимости от настроек, и прочее.
Понимаю, поэтому и спрашиваю :) У меня нет большого опыта промышленной работы, поэтому мне трудно взвесить реальные риски того или иного решения. Когда мне приходится писать такого рода класс, каждый раз прохожу примерно такой путь:

Первая мысль. Сделаем быстро! Есть уже классы комбо-бокса, списка и тому подобных гуёв. Запихну их всех в свои объекты и нечего думать, пусть у меня будет ComboBox* mNumber вместо int mNumber + внешний ComboBox.
Вторая мысль. Класс уже не будет библиотечным, т.к. он привязан к конкретному гуи. Проще переписать класс при надобности или делать его изначально переносимым — чёрт знает.
Третья мысль. Да меня ж товарищи распнут, если я засуну комбобокс в класс! Так никто не делает! Лучше уж сто функций синхронизации — да, жирно, зато по науке…
> Запихну их всех в свои объекты и нечего думать

… а потом про вас напишут на WTF :)
На мой взгляд, проблема здесь в том, что, приступая к программе, следует задаться двумя вопросами (о них есть у Голуба [2]):
1) Какие возможности должно поддерживать это приложение?
2) Как реализовать эти возможности простейшим способом?

С моей точки зрения, неверно писать приложение на все случаи жизни. В пределе, мы получим сумасшедший интерфейс в стиле M$ OLE/COM, который пытается быть интерфейсом-на-все-случаи-жизни, но, в результате, ни одну проблему не решает простым и надёжным способом.

Да, ваш класс привязан к ГУИ. И это правильно! Потому что он работает с ГУИ. Данным конкретным ГУИ. В данной конкретной программе. Вы решаете конкретную задачу, которая есть в данной конкретной работе.

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

Мой совет. Смотрите на форматы doc-файлов. Или на интерфейсы типа ОЛЕ. Путь вас передёрнет, пусть это будет отличной прививкой от решения неуместно общих задач в конкретном приложении.

Конечно, всегда нужна гибкость, нужно применять мозги. Где-то быть готовым и так далее. Но мораль всего моего комментария такова: решайте конкретную проблему, ту проблему, которая перед вами стоит. В конечном итоге, это экономит время. Именно экономит. Потому что перетаскивание якобы-общих-решений в другие программы всё равно приведёт к тому, что вы кусками будете переписывать — потому что там одно не учли, там другое. Проходили — знаем.

Разумеется — это моё мнение.
UFO landed and left these words here
К сожалению, в данном случае конкретная реализация будет сильно заслонять собой идею. Выложил для примера код Бункера, возможно, это будет хотя бы немного полезнее общих слов из статьи:
habrahabr.ru/blogs/cpp/111120/#comment_3548011
> Мой опыт показал: проще реализовать всю кухню внутри одного класса, чем искусственно делить на некие абстракции, которые к тому же слишком сильно связаны, что грозит адскими интерфейсами

Пример: я хочу использовать вашу систему объектов с её, судя по описаниям, невероятно сложной алгоритмической нагрузкой. Но с небольшим отличием: я хочу разместить ваш код на сервере приложений и дёргать его через веб. Вопрос: зачем мне впились ваши Button и ComboBox в классах бизнес-логики?
И создает совершенный ад при тестировании.

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

Т.е. препроцессор не нужен? И флаги в компиляции это от лукавого? Source based дистрибутивы на этом и строятся и даже собираются причём кому как нравится.
скажите мне, как мне написать на C++ прошивку для AVR микроконтроллеров c переносимостью в приделах семейств Classic и Mega без использования #ifdef?
Одно дело в нескольких заголовочных файлах (в очевидных местах) прописать препроцессор, а другое дело прописывать в работе класса. В любом случае препроцессор следует использовать с умом, а не лишь бы работало.
Да, с AVR никуда не денешься. Факт.

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

А переключения вида:
#define V_MFC
#define V_GTK
#define V_QT
#define V_HTML
вызывают глубокое чувство противоречия.

С С++ общаюсь больше 10 лет, но достаточно эпизодически.
Будем считать, что это знание осталось мне не доступным.
Условная компиляция, конечно, не панацея. Я бы предложил решать конкретную задачу, а не делать решение на все случаи жизни. Всё вышеописанное — без фанатизма, конечно.
habrahabr.ru/blogs/cpp/111120/#comment_3543799
Это вы серьезно насчет все это делать в одном классе? Из коллег еще никто не застрелился?
Говорил же себе: не ставить плюс, пока не прочитал до конца.
Отсутствие полезных советов. Они конечно есть: заведите тетрадь для каждого заказчика и используйте инкапсуляцию. Чтоб это понять не нужно иметь 10 лет программирования на С++.
Больше конкретики: что вам помогло решить проблему над которой вы месяц бились или технология компиляции в верхних слоях головного мозга.
Как показывают комментарии к статье, она заставила многих задуматься о том, как они применяли инкапсуляцию до этого. На эту тему уже несколько веток обсуждения.
Я бы с радостью добавил ещё материала, но, видимо, правильно не стал. Нам бы с этим пунктом пока разобраться. Если Вы имеете практический опыт по правильно реализованной инкапсуляции, пожалуйста, помогите с комментариями. Их слишком много, я не успеваю всем отвечать.
Тетрадь? Фи, мон ами, в наш просвещённый цифровой век это дурной тон :)
А вот и зря. В тетради можно порисовать, причём со скоростью мысли. На компьютере это недоступно, если у вас, конечно, нет вакома, но большинство программистов ими, как правило, не пользуются. Так что тетрадь — верный выбор.
а чего рисовать-то? вы программист или художник? схемы/диаграмы/блоксхемы/наброски gui легче рисовать в visio — если не пробовали, попробуйте, убедитесь сами.
Пробовал. Если мы в стадии поиска, visio — не самый подходящий инструмент. А если мы уже готовы делать схемы, тогда вполне.

Просто этапы бывают разные, и задачи бывают разные. А ещё иногда нужно нарисовать что-то нестандартное. Либо полностью нестандартная схема (которая и не схема вовсе), либо какая-то деталь. Я временами просто рылся в внутренностях таких программ, вместо того, чтобы за пару секунд нарисовать нужное на бумаге.

Visio, безусловно хорош, но он не панацея.
Не вижу, как бумага может быть лучше visio на стадии поиска решения. Ведь там, где Вы будете чёркать бумагу, в visio можно просто удалить блок. Аналогично, когда схема становится больше, на бумаге придётся её перерисовывать, а в visio — просто переупорядочить и всё. Короче, я не понимаю, каким образом бумага может быть лучше.

> Либо полностью нестандартная схема (которая и не схема вовсе), либо какая-то деталь.

Например?
Нескромный вопрос: почему вы решили, что вам есть чему научить других? только изза 10 лет?

В топике невнятно и неструктурировано изложены азы ооп. Почемуто постоянно упоминаеться с++, хотя ничего специфического для с++ не рассмотрено.
Непонятно зачем использована своя терминология «бункер», «датчик», «цех»…
«Этюды» явно дают понять, что автор не знаком с ТДД.
Полезной информации ноль.

подумайте хорошо стоит ли писать следующую часть.

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

Цикл статей о программировании — это конечно хорошо, но без законченной мысли и структурного изложения — все это бессмысленно.
Что ценного я лично вижу в своей статье:
1) Напоминаю об делении на куски и отладке по кускам. TDD — лишь один из вариантов тестирования этих кусков, который, конечно, имеет право на существование. Я специально нигде не навязываю стиль создания этих элементов, потому что каждому ближе своё, всё зависит от навыков и специфики проекта. Мне, например, ближе тестирование по граничным условиям, а потом уже переход к автоматизированным тестам, по необходимости. Это позволяет идти от задачи и экономить время, так как я не делаю развёрнутое тестирование там, где оно, в ходе отладки этюда, становится ненужным.
2) Рассказываю о важности переносить сложные алгоритмы на бумагу и отлаживать их на бумаге, для начала — в простом русском языке.
3) Напоминаю о простой методике создания структуры классов.
4) Предлагаю использовать инкапсуляцию *правильно*, объясняю почему так следует делать на жизненных примерах.
5) Заранее готовлю начинающих к тому, что переписывать с нуля — это нормально.

Для вводной статьи, по-моему, вполне достаточно. Обсуждения выше и ниже показывают, что не стоило добавлять ничего больше, чтобы не мешать обсуждение самого сложного момента статьи — инкапсуляции — с другими темами.
Именно эти ценные советы мне дали тогда, когда я впервые сел за компьютер писать серьезный проект.
1. ваш ответ только утвердил в мнении, что с ТДД вы незнакомы.
2. Алгоритмы для работы с геометрией я рисую (так как рисунок — язык геометрии). А остальное да и еще «в простом русском языке»....?? Я когда пишу код думаю на том языке, на котором пишу код. И держу в голове схему классов. Если надо обяснить комуто — рисую UML.
Вы действительно думаете, что для написания алгоритмов русский лучше чем языки программирования? Может вам просто тяжело даются ЯП?

3. Повторили первое предложение из любой книжки типа «ООП для чайников».
4. «выведи себя в интерфейс, обработай нажатие мышки, сохрани себя в XML и т.д.» — Это не правильное использование инкапсуляции, а отсутствие декомпозиции. В народе «гавнокод».

С нетерпением жду следующий топик про «избавление от смарт поинтеров».

> Предлагаю использовать инкапсуляцию *правильно*, объясняю почему так следует делать на жизненных примерах.

Хм, подтвердите, пожалуйста, ваши слова насчёт правильности и того, что так _следует_ делать? Желательно конкретными цитатами из классиков ООП. Опыт ничего не значит, потому что у каждого он свой. Кто-то может десять лет писать вермишель.
Да бросьте, не так много хороших тематических статей на Хабре, может, из нескольких частей будет что-то толковое, зачем рубить на корню.
Следующая статья должна быть про «ненужность смарт поинтеров». Учитывая то, что смарт поинтеры — best practice практически все время существования с++, уровень автора не позволяет мне подумать, что он действительно придумал чтото лучше. И мне уже страшно, что он напишет. И тем более, что ктото в это поверит.

Согласен, заявка про ненужность очень интригующая.
К чему столько негатива? Вы написали несколько неплохих казуальных игр. Это замечательно.
Насколько я могу судить по скриншотам, мой код использовался в тайтле класса ААА даже после моего ухода из команды во 2 и 3 частях игры, в течение минимум нескольких лет. Если вы представляете себе цикл разработки крупных игровых проектов, вы должны знать что это означает.

Предлагаю не судить по т.н. «уровню» только исходя из того, что автор предлагает подходы, отличающиеся от ваших. Это первое.

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

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

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

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

Я не сужу по заявления «ААА», «10 лет» — для меня это ничего не значит. Я читаю что вы пишете, вижу, что это наивно/неправильно/непрофессионально и делаю выводы об уровне.

«Давайте уважать друг друга» — я вас уважаю. Но это не мешает мне критиковать ваш топик.
Предлагаю перевести наше с вами обсуждение в практическую плоскость и перейти к конкретным примерам. На них обсудить чей подход лучше.
Ок. задача «Написать универсальный сортировщик, который сортирует любые обьекты любого типа по заданым условиям» — пойдет?
Сортируют обычно в каком-то массиве, либо другом контейнере. Это должно явно указываться в задании. Постановка невнятная, тем более для того, кто претендует на какой-то уровень.
сортировка в любом контейнере, в котором впринципе сортировка возможна ;)
Ну, если для вас такое сочетание слов называется постановкой задачи, то я даже боюсь предположить как вы программируете. )
а мне этого достаточно для работающей реализации.
наверное это изза низкого уровня ;)

этим комментом вы расставили для меня все точки над і. буду ждать ваш следующий топик.
Наверное, вы этим что-то хотели сказать.
Однако, у вас не получилось.
Я имею ввиду — если вы можете объяснить, чем крупные игровые проекты отличаются от крупных неигровых — объясните. А без объяснений ссылка на википедию выглядит глупо.
UFO landed and left these words here
Спасибо, вы очень хорошо выразили мою мысль. Мне не удалось так чётко ответить по поводу ТДД.
Тесты, это такой хитрый способ записи требований, который одновременно понятен и нормальному человеку (не программисту) и машине. В идеале — с первого раза ни у кого не получается. ;) Поэтому тесты в ТДД пишутся раньше, чем код, как blueprint того, что ты должен сделать.

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

Что должен делать «Бункер»? Обработать нажатие мыши — требование раз. Сохранить себя в xml — требование два,… Записать это на языке тестов не составит труда. А после этого, их можно запускать — в любом количестве, в любое время, вручную поштучно и автоматически по over 9k за раз, на всем протяжении жизни проекта. Что там еще было? — не открывать внутренней информации другим классам — хрень, а не требование, оно не формализуется. Есть расхожее мнение, мол ТДД обеспечивает правильность проектирования — это миф. Тесты сами — по себе, проектирование — само по себе.
UFO landed and left these words here
Отнюдь не претендую на абсолютную истинность или новизну суждений — просто мнение, которое у меня сложилось к данному моменту. Я занимаюсь web-программированием и активно использую автоматические тесты лишь последние пол. года. Раз уж согласились с тезисом о требованиях, может быть проясните и спорный момент с проектированием? =)

Я рассуждаю так. Допустим, есть некоторая задача, которая стоит того, чтобы её решить. Допустим — найти ответ на Главный вопрос жизни, вселенной и всего такого. Задача сложная и как писать код вообще совсем не понятно. Однако, это уже какая-то информация. Данные факты — то, что задача уже поставлена, и то, что непонятно как писать код — можно записать в виде теста на всем понятном языке:
void testShouldAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything() {
    throw new SkipTest()
}

Профит в том, что данная конструкция уже одним своим существованием будет напоминать о необходимости что-то так-и родить, при каждом запуске тестов. Можно пойти дальше, и сочинить сценарий решения, например с участием суперкомпьютера и удивительным ответом в конце:
void testShouldAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything() {
    DeepThought supercomputer;
    long answer;
    answer = supercomputer.calculate();
    assertEquals(42, answer);
}


И так, реализации ещё нет, но тест уже есть. Все в рамках ТДД. Вот только не поняно — я все правильно спроектировал, или нет? Может быть решатель нужно было представить в виде простой функции, а не объекта с методом? И не на с++ а на lisp? Верен-ли ответ? Как проверить?

Получается, что тесты являются лишь способом записи решений, принятых в процессе проектирования. Но каким образом они устраняют ошибки проектирования? — Мне не понятно.
Человек вроде чётко написал: «если у вас есть ошибки в проектировании, то ТДД ничем не поможет».
UFO landed and left these words here
Да, так и есть: другое решение, нужно описывать заново — я периодически попадаю в такие ситуации. По-моему, это в порядке вещей — все равно нормальное решение получается лишь с третьего раза. :)
Почему бы тогда классу не заняться самотестированием? В конечном итоге, мы всё равно три раза перепишем функционал. Но по ходу, зачастую, не будем переписывать интерфейс обмена между классом и его «проверщиком» по ходу изменений внутри класса. По вашему примеру это будет примерно так:
class SelfTested
{
protected:
    virtual void SelfTest() = 0;
};
class DeepThought : SelfTested
{
protected:
    virtual void SelfTest()
    {
        int answer = Calculate();
        assertEquals(42, answer);
    }
private:
    int Calculate();
};


В этом случае, если даже логика изменится и внутри я Calculate поменяю на AskAnotherSupercomputer, либо буду ожидать строковый ответ, вовне объекта ничего не поменяется.
UFO landed and left these words here
Виртуалки приведены лишь «для красоты». Основная идея — не в них, а в инкапсуляции внутренней кухни.

Что касается смешивания в одну кучу. Я предлагаю такую связку:
класс {внутренние данные; проверка;}
только потому, что это короче, читабельнее, и не минимизирует число изменений по сравнению с этим:
класс {внешние данные;} + класс_проверки {указатель_на_объект_класса; вызов_внешних_данных_класса; проверка; }

При очень большом желании «не смешивать в одну кучу», определение функций-членов для тестирования класса можно вынести в отдельный файл. Это, на мой взгляд, всё равно лучше связки №2.
*и минимизирует число изменений…
UFO landed and left these words here
Взаимодействием объектов заведует другой класс. Класс более выского уровня в иерархии. В него можно точно также включать код для проверок взаимодействия дочерних объектов.
Или я что-то упускаю?
UFO landed and left these words here
Не знаю таких классов, поэтому ничего не могу сказать об их взаимоотношениях в рамках используемой вами иерархии.
UFO landed and left these words here
Если речь идёт о синтетическом тестировании операторов взаимодействия между этими классами, то ничто не мешает сделать тесты функциями-членами (в т.ч. статическими), точно также, как и сами операторы вычитания и т.д. Либо — да, вспомогательными, отдельными дружественными функциями (что, по сути, другой вид записи закрытой статической функции-члена).

Если речь идёт о каком-то конкретном применении объектов этих классов в рамках другого класса, то тест вполне можно сделать внутри этого класса.

На мой взгляд, спор перешёл в чисто эстетическую плоскость. Предлагаемый подход, думаю, вам понятен. Применять или не применять — решать вам.
Будут конкретные вопросы по практическому применению подхода — пишите, готов обсуждать.
UFO landed and left these words here
Наружу открываются функции-члены, которые просят класс выполнить ту или иную работу: выведи себя в интерфейс, обработай нажатие мышки, сохрани себя в XML и т.д.


Это ж каша получается, а не код. Этих методов вообще не должно быть в классе. Извините, но это как раз руководство как делать не стоит. Пусть у меня опыт программирования и мал, но его мне хватило, чтобы пытаться как можно больше разделить логику и интерфейс пользователя. Советую почитать Макконнелла, там про 10 лет опыта есть хорошое высказывание.
В случае разделения логики и управления интерфейсом, Вам приходится «размазывать» семантику класса по ряду других объектов. Это увеличивает код за счёт интерфейсов-сцепок. Зачастую это также разносит логически близкий функционал по разным файлам, что затрудняет чтение кода. И, наконец, когда Вы будете что-либо менять в своей программе, Вам придётся менять не только содержимое классов логики и интерфейса, но и интерфейс между ними. Почти всегда это можно организовать в одном классе, в одном файле спп.
Ни в коем случае не навязываю свой подход, но подумайте, взвесьте ещё раз все «за» и «против» — возможно, объединение логики с интерфейсом в едином классе, не так уж плохо, особенно когда речь идёт о средних и небольших интерфейсах пользователя.
В вашем случае, если меняется внешний вид, вы точно так же должны пройти по всем классам типа бункера и изменить в них код, который выводит что-то в окно.
Не обязательно.

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

Я автора понимаю, у меня уже давно сложилось представление, что код ряда сущностей мощно размазан по проекту. Вот тут они работают, вот тут выводят данные, вот тут ими управляют. И в случае изменения требований менять приходится сразу везде. Класс, вывод, управление. И это всё в разных местах. Так что определённая логика тут всё же есть.
если следовать вашим советам — практически все паттерны можна выбросить.
Это не "«размазывать» семантику класса" — это декомпозиция.
ну не стоит так хаять декомпозицию, в нагружаемых проектах в БД проводят декомпзицию для скорости.
автор там гдето выше сказал, что нужно четко понимать, что вам нужно сделать — если у вас задача каждый полгода отрывать гуй или совместно его использовать в нескольких проектах, то стоит применить другой подход.
но если у вас задача сделать конопочку, а под ней будет лежать большой пласт задач, то может не стоит обзаводить кнопочку рюшечками?
про паттерны: в обсуждении тоже промелькнуло, что велосипеды можно и нужно строить если в итоге они получатся быстрее и лучше.

вобщем стоит подождать вторую статью — там нам обещали показать, как этот подход может стать граалем…
вы не путаете декомпозицию з денормализацией?

Я вообщето декомпозицию отстаивал, а не хаял. А автор ее просто не умеет готовить и называет "«размазывать» семантику класса".
мммм, ну скорее я сравнивал подход — разделение большой сложной задачи на малелькие простые и разделение набора данных для простых и быстрых запросов.
с терминами действительно перепутал >_<

ЗЫ кстати, а может вы тож статью напишете о «своей» методе или книжки подскажите правильные?
Не думаю, что в статью можно втиснуть то, что написано в 5 книгах. Ну и не дорос я еще, чтоб учить других «как писать программы».

посоветовал бы GoF Design Patterns — про то как надо,
горький вкус java — про то как не надо и
Pragmatic Programmer — про все.
Вы зря так категорично. Нужно понимать уместность каждого подхода в программировании. Отделение имеет смысл, когда предполагается взаимозаменяемость компонентов. Излишняя универсальность — неоправданное усложнение программы со всеми вытекающими.
Статья достаточно сумбурная. Я более менее понял то, что Вы имели ввиду, хотя я уверен некоторые начинающие программисты вполне могли и запутаться. Если Вы её расценивайте этот пост просто как введение, то возможно да — ок. Интересно посмотреть, что будет дальше.

Я всегда обоими руками ЗА, когда человек хочет поделиться своим опытом. Книжки книжками, статьи статьями, а опыт сразу не приходит. Нужна общаться, искать, советоваться, консультироваться.

интересно почитать Ваше видение по
«прощаюсь с проблемами new/delete, избавляюсь от необходимости в умных указателях и мусоросборщиках, заменяя их на более простую и естественную методику работы с ресурсами.»
было бы здорово если бы статья была очень подробная
Приношу извинения, если статья правда сумбурная.
Я, правда, рассчитывал на внимательное и «с карандашом» её изучение.

Что касается следующей части, обязательно постараюсь учесть Ваши комментарии. Буду признателен Вашим конкретным предложениям по упорядочению данной и последующих статей.
Сейчас попробую высказать своё, имхо, как бы можно было изложить. Скажу сразу, я начинающий разработчик и опыта у меня мало. Соответственно, мнение моё очень субъективно. Можно попробовать рассказать о какой-то конкретной проблеме, задаче, которую вы решали. Наверняка были какие-то подходы к решению, выбран был один вариант, потом пришлось выбрать другой подход или пришлось что-то перепроектировать. Такие ситуации я уверен возникают.

Или, например, для какого-то проекта показать анонсированную Вами «естественную методику работы с ресурсами» и как она была использована в конкретном проекте и почему именно так и никак иначе. Рассмотреть её эффективность, какие плюсы, какие минусы. Было бы интересно, мне по крайней мере.
если прочитать коменты, то все разъясняется, кроме того в коментах сразу разъясняються и другие подходы к проектированию
«Здесь у нас объекты, которые суть чёрные ящики. Каждый объект — сам себе жнец, швец, на дуде игрец. Напоминаю, что get/set доступ — это чуть более завуалированный вариант открытого члена класса. С этим подходом нам не по пути. „

Банальный пример: Есть чистый абстрактный класс фигура (CShape), от него наследуется 2 класса — прямоугольник (CRectangle) и эллипс (CEllipse). Мне нужно получаться координаты этих фигур. В классе CShape координаты x и y определены в private. А в public есть два метода getX(), getY(). Соответственно для CRectangle и CEllipse они доступны через наследования. Ну дальше тут всё понятно.

Как тогда обойти этот путь?
“просим объект сделать нужное действие самостоятельно»
Если, например, это в дальнейшем нужно будет использоваться для вывода на холст примитивов. Нам нужно наоборот абстрагироваться от того же самого холста. И создавать классы как будто нет никакого холста. Я правильно понимаю или ошибаюсь? Объясните если неправ.
Вы взяли отличный классический пример. Он поможет очень быстро объяснить суть идеи.
Давайте задумаемся. Задумаемся как профессионалы — практики. Зачем в чистом абстрактном классе координаты? Почему Вы заставляете всех наследников Фигуры иметь математическое описание через точку на декартовой плоскости? Подумайте об этом искусственном ограничении, которым Вы сами себя связали.
Что Вы хотите на самом деле? Вы хотите, чтобы фигура выводилась в указанным координатам. Что это? Правильно, это интерфейс, то есть класс с чисто виртуальными функциями-членами:
class Shape
{
public:
virtual void Paint(int x, int y) = 0;
};


А что если мой объект — сплайн, описываемый вещественными уравнениями? Ему не надо навязывать внутреннюю структуру, завязанную на паре целочисленных координат. У него вообще нет такого понятия как «координата центра».

Мораль такова: не нужно создавать лишнее сцепление там, где оно не нужно. Объект сам отлично разберётся что и как ему иметь внутри. Ваша задача — продумать хороший общий интерфейс.

И последнее. Наследование само по себе — это довольно сильное сцепление. Лучше проектировать сравнительно «широкую» иерархию классов, чем делать наследование там, где без него можно обойтись.
я не самый опытный программист, но предположу, что в вашем случе получается дублирование кода в классах с координатами цента (Rectangle и Ellipse), что дает возможность забыть продублировать изменения, если когданибудь этот код придется изменить.
может тогда ввести ещё два класса для шейпов с координатами центра и для шейпов без координат центра?
Как вариант — пойдёт. Конечно проще смотреть на код, тогда скажу как, на мой взгляд, лучше :)
да, естественно лучше смотреть на код, но в данном случае коментарии абстрактны, как и топик ;) вобщем ждем продолжения и практических примеров.
Единую для разных классов логику можно вынести во внешний интерфейс, который сами объекты и будут дёргать по необходимости.
шейп рисует сам себя? вы в каком геймдеве работали? зачем шейпу знать о рендере который его будет рисовать? а если завтрарендер поменяется, незнаю как 5 лет назад, а сейчас это в порядке вещей…
Не писал на С++ уже довольно давно и пост начал читать с надеждой освежить или обновить знания. Но блин, уж сколько всего я успел забыть, но это же азбука, тем более, как уже заметили, относящаяся не только к плюсам.
Не хочу обидеть, но хотя бы проработать статью стоило получше.
Вы совершенно правы, это азбука. Именно с азбуки я и начал цикл статей. Потому что более сложные вопросы будут базироваться на изложенном здесь.
Gолное пренебрежение открытыми членами класса мне лично не нравится. Да есть случаи, когда уместнее использовать дружественные функции. Но использовать их всегда ИМХО неверно. Хотя у каждого человека есть свой метод, и для него он — правильный.
В статье я говорю о передаче моего опыта и моих наработок. Отдаю себе отчёт, что множество других людей (наверняка часть из них умнее и опытнее меня) пишет иначе. В статье я решил поделиться тем, что имею. Потому что оно работает и неплохо себя зарекомендовало. Так или иначе, автор ещё студентом работал в одной из ведущих гейм-девелоперских студий страны, а потом заменил собой почти весь штатный отдел программистов в одной из лидирующих компаний — автоматизаторов мелких и средних предприятий. Возможно, этот опыт будет кому-то здесь полезен.
мне кажется автор придает слишком большой смысл цифре 10…
Когда объект реального мира содержит другой объект реального мира (только один), в С++ применяется наследование классов.

Вы говорите вроде бы о понятии «has a», но наследование-то соответствует понятию «is a».
Примеры приведены интересные, правда едва ли я бы посоветовал Вашу статью начинающим программистам, слишком поверхностно и сумбурно.
Скорее Вами руководил богатый личный опыт и желание поделиться им, но не желание действительно научить…
По первому пункту. Вы приводите канонический подход, и он, безусловно, правилен. В моём случае, я привожу более простое мнемоническое правило, которое, хотя и не так красиво звучит, но включает и ваш подход, и позволяет обрабатывать случаи, когда взаимосвязь «содержит», а не «является», но экземпляров содержимого может быть только один.

Вы правы в том что касается сумбурности и краткости. Слишком много сложного материала дальше. Приходится делать введение кратким. Ни в коем случае не пытаюсь заменить собой классические книги, приведённые в списке литературы. Моё дело — заинтересовать, вызвать желание разобраться. Желающие смогут разобраться «с карандашом» с моей статьёй и книгами, либо задать вопросы в комментариях.

За ценное замечание — спасибо.
Мне кажется, неправильно объединять понятия «has a» и «is a» в одно мнемоническое правило — слишком важные отличия, тем более что в С++ есть разное наследование, отвечающее за то и другое. Квадрат содержит прямоугольник, но не является им — пример взят из Майерса.

Кстати, отсюда вопрос — получается публичным наследованием Вы не пользуетесь? Если рассматривать другой классический пример ООП: «гражданин» с паспортом, именем и фамилием и, скажем, «студент» с данными об учебе. Как будут в этом случае будут распределен код взаимодействия с гуи и, к примеру, сериализация в xml? Заново переписаны в «студенте», чтобы обеспечить независимость этого класса или все же будут опираться на реализованные раннее версии в «гражданине»?
это более простое мнемоническое правило некорректно. Простой пример: есть класс Logger, содержащий в себе некий объект, наследующий интерфейс IWritable. По вашему правилу получается, что Logger должен наследоваться от IWritable, хотя в классе Logger должны быть методы LogInfo, LogError, которые дополнительно пишут, например, время записи, а просто Write не нужен ни самому классу, ни человеку, использующему Logger.
Так в чём вопрос, используйте protected-наследование.
Буду ждать второй части, мне как раз интересны темы работы с памятью, отслеживание утечек и достижения высокой производительности в программах :) спасибо
лучше поищите доку от Сони pitfalls of object oriented programming
и что нибудь об Data Oriented Design и Aspect Oriented Programming чтоб зря не терять время в ожидании неизвестно чего…
Есть ряд полезных мыслей, хорошо изложенных. Причем по моему мнению это больше относится к самому подходу к работе — ведению документации, написанию отдельных консольных программ чтобы оттестировать алгоритмы. Подобный порядок вызывает уважение. Но при этом изложен ряд спорных с ООП-утверждений.

Итак, Бункер не выдаёт наружу подробности своей реализации. При этом, он сам выводит себя в интерфейс, сам обрабатывает нажатия на свои контролы, сам выполняет всю работу, которая требует знания о том, как он устроен. Наружу открываются функции-члены, которые просят класс выполнить ту или иную работу: выведи себя в интерфейс, обработай нажатие мышки, сохрани себя в XML и т.д.

Если с сущностью выполняется много операций — то интерфейс класса превратиться в мясо. Поэтому сериализацию и подобные операции имеет смысл разнести как минимум по разным интерфейсам, а порой и по вспомогательным классам. А насчет «выведи себя в интерфейс» — по моему мнению вообще за гранью добра и зла.

«Если с сущностью выполняется много операций — то интерфейс класса превратиться в мясо»
— если операции за сущность будут работать другие сущности, это будет ещё большее мясо, разнесённое, к тому же, по разным файлам, что ещё больше затрудняет прочтение кода.

«Поэтому сериализацию и подобные операции имеет смысл разнести как минимум по разным интерфейсам, а порой и по вспомогательным классам.»
— Что мешает спрятать сериализацию в классе? void Entity::Serialize(Stream *);
Всё отлично работает. Если сериализацию делает внешний код, в него требуется передать всё содержимое объекта. А зачем? Проще хранить внутри.

«А насчет «выведи себя в интерфейс» — по моему мнению вообще за гранью добра и зла.»
— Подумайте, пожалуйста, об этом:
habrahabr.ru/blogs/cpp/111120/#comment_3543396
1. «если операции за сущность будут работать другие сущности, это будет ещё большее мясо, разнесённое, к тому же, по разным файлам, что ещё больше затрудняет прочтение кода»
— Когда я хочу посмотреть как работает основной функционал класса, то меня мало интересует сериализация, его представление в виде контролов и прочее. Поэтому, пусть лучше будет пять логически разбитых файлов(классов) по 200 строк, чем один на 1000, где все это будет вперемешку. И 1000 — далеко не предел.

2. «Что мешает спрятать сериализацию в классе? void Entity::Serialize(Stream *); Всё отлично работает»
— Можно писать вообще весь код в одном классе в одном файле и все будет работать:)

3. «Подумайте, пожалуйста, об этом:»
— Я подумал. Возможно, у вас встречаются более специфичные системы, где такой подход имеет право на жизнь, потому-то я и написал «по моему мнению». Но вот представьте, если вашему классу нужно иметь несколько графических представлений? И как такой подход дружит с разнесением программы на слои, где классы самого абстрактного уровня вообще ничего не должны знать о гуишных шнягах?
1. Это бы отлично работало, если бы каждое изменение функционала сущности не пришлось бы отслеживать в остальных классах. Но вполне допускаю, что в сложных случаях я бы разбил функционал по разным файлам, минимизируя сцепки между ними за счёт обобщения интерфейсов. Если уж сильно хочется разнести по файлам — возможно, я бы разнёс реализацию функций-членов класса, чем создавал лишние сущности в виде новых классов. Предлагаю этот пункт перевести на конкретные примеры.

2. :)

3. Отлично дружит. Приведите коротко конкретный пример класса, который будет иметь несколько представлений, я покажу как подружить его с правильной инкапсуляцией.
1. Толковое разбиение функциональности как раз и приведет к минимально зависимым компонентам.

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

Если требуется динамическая взаимозаменяемость интерфейса, продумал бы общий абстрактный интерфейс, три класса интерфейсов (веб, ГУИ, консоль) отвёл бы от него, а мой общий класс работал бы с указателем на одну из реализаций.
Дополню: если бы сам класс при этом получался слишком большим, я бы делал объекты-помощники (точно также закрытые, с доступом через открытые функции-члены), которые включил бы в свой класс.
Тогда придется признать что класс — это не отображение физической сущности, а некоторое логически объединенное количество данных и методов. С чего и начинали:)
С моей точки зрения, Вы вполне правы.
Всё зависит от риторики и от точки зрения, например, на человека как единую сущность или как композицию более простых элементов и узлов. То есть, ваше утверждение не противоречит моему, всё зависит от того как посмотреть и как что назвать. Вам ближе рассматривать поэлементно — не вопрос. )
Да, это скорее к вопросу о деревянном столе — имеет ли смысл рассматривать, что он отдельно деревянный, и что он мебель.
Вообще я против словоблудия, и если вы об этом же, то я с Вами согласен.)
В статье я дал простое мнемоническое правило, которого вполне хватает для новичков.
Никто не отказывается ни от объектов-помощников, ни от ограничений применимости любого правила (как мы знаем из формальной логики). Правила голову не заменят. Но, с моей точки зрения, приведённое правило — неплохой старт.
Кто может привести более простое, правильное и понятное для новичков — буду рад услышать и использовать в дальнейшем.
Я бы вашу статью не показывал новичкам, честное слово… У вас концепция, которая может быть вполне жизнеспособна при правильном использовании, но при этом очень отличается от общеизвестной. К примеру как экстремальное программирование.
А новичкам лучше сначала давать стандартный подход.
С моей точки зрения, хотя я возможно и не прав, для новичка оба подхода одинаково сложны. А вот для тех, кто привык к стандартному подходу, переключиться на другую концепцию непросто.
В этом смысле, третья часть, в которой я планирую затронуть многопоточность, будет такой же неуютно-непривычной.

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

P.S Не сказать, что я сам собираюсь использовать его (подход) в коммерческих приложениях, но академический интерес есть :)
Конечно не против, давайте попробуем всё сказанное на практике — к этому я и призываю.
Видимо совсем давно ушли из геймдева, ато за последнее время уже все, кто пишет о геймдеве постарались вылить ведёрко говна в сторону ООП + C++. Тк типичное использование этого крестового ООП только понижает продуктивность программиста и производительность приложения на современном железе, в особенности на консолях.
Что касается продуктивности — вопрос спорный. Готов обсудить подробнее, на тех примерах, которые Вы приведёте. Доводы в защиту моего подхода я привёл в самой статьи.
Что касается оптимизации — здесь согласен на 100%. На эту тему я даже писал статью. Но, согласитесь, это немного иной вопрос, выходящий за рамки введения в методики программирования.
За последние лет 8 уже очень много слов было сказано по этой теме «entity system», «component based game object» и куча других терминов, которыми описывают одно и тоже.
www.gameenginebook.com/toc.html (глава 14.2), здесь очень хорошее описание проблем с крестовым ООП.
За ссылку спасибо, почитаю на досуге. Тогда смогу что-то сказать.
В нашем случае, действительно, была структура, построенная на сущностях. Но главный программист, которого я считаю очень талантливым человеком, реализовал свойства сущностей динамически, кроме того, к ним применялось позднее связывание. Это, конечно, решало ряд проблем, хотя и усложняло код. Возможно, с тех пор придумали что-то лучше — буду смотреть.
<<… Если я пишу программу, которая использует неотработанную технологию или элемент, я сначала делаю этюд ..>> — в джава искпользуется unit test, для каждого программиста который newbie, насколько я понял цикл вышых статей расчитан именно на c++ newbies, намного удобнее чем писать свои этюды. То есть если человек совмещает в себе на первых порах роли developer-a и QA — намного удобнее использовать джава для изучения простейших приемов Test Driven Developement. И еще — важно на мой взгляд учитывать с какого собственно говоря языка человек переходит к изучению С++ — с php, java, ruby, с нуля…
Реальный опыт всегда интересен, даже если он повторяет известные вещи, так как в этом случае всегда вносится свежая струя.
Ждем продолжения.
Спасибо за поддержку!
Буду рад любым отзывам попробовавших на практике. Предлагаю честно обсуждать все спорные вопросы, которые возникают при практическом их применении.
исключение подтверждающее правило — это феерический бред, который все как бараны петросянят друг за другом.

что плохого в свойствах? это просто ручка регулировки. как на них реагировать решает сам объект. может хоть вообще игнорить или передавать другому объекту. я не очень помню как там в цпп, но в пхп можно делать так: $obj->prop+= 10 и это будет нормально перехвачено.
если почитать статью, то становится ясно, что автор имел в виду.
отсутствие сеттеров приводит к слабому взаимодействию между классами, а значит более быстрая масштабируемость и быстрый рефакторинг.
в случае, когда класс внезапно передумал быть машей, а стал сашей, то все классы, которые до этого дергали свойство Masha->SetSiski придется переписать. И нет никакой гарантии, что это не повлечет за собой цепочку других изменений.

В принципе верно, инкапсуляция, только более строгая. Подход как минимум имеет право на то, чтобы быть. Правильно или нет — это уже вопрос применимости в той области, где ты работаешь.
ага, был класс рисования графиков, а стал класс чтения файлов.
:) на практике бывает всякое. Как говорит автор — передача по сети внезапно добавить или чтение xml потока. и все, логика класса неумолимо изменяется и привет, методы и свойства не всегда можно сохранить неизменными

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

Ну а про внезапное добавление — ru.wikipedia.org/wiki/Декоратор_%28шаблон_проектирования%29
На практике происходит вообще всё что угодно.
Например, в классе Бункер у меня находился алгоритм управления, который я отлаживал два месяца на реальном оборудовании. И у меня не было никакой возможности и желания всё это трогать. Так что пример очень даже реальный: слабое сцепление может сэкономить кучу времени, при всей кажущейся неканоничности подхода.
Понимаю, что отказ от открытых данных кажется необычным и рискованным. Он требует переосмысления самого процесса проектирования классов и их взаимодействия. Всё что я предлагаю — это попробовать. Вдруг, этот подход окажется на практике не таким и узкоспециализированным. )
отсутствие открытых данных лично мне не кажется рискованным. вот контролы в классе — это страшно:)
А чего такого страшного? Если фреймворк позволяет, то вообще не проблема.
В случае всяких СиБилдеров и прочего, да, приходится учитывать время жизни контролов и порядок инициализации. Зато при каждом чихе и апдейте вы не правите вот такие вот интерфейсы
void MyEntity::UpdateFromUI(Combo *combo1, Combo *combo2, Button *button1, Button *button2, Button *button3, EditString *editor1);
и не нужно. пусть будет контроль над одним контролом, но не непосредственно внутри класса, а через посредника
имхо, естественно.

Можете привести пример? А то я уже теряю нить. )
ну я чуть пониже дал пример habrahabr.ru/blogs/cpp/111120/#comment_3543699 — один класс для контроля за гуйней, там и синхронизации и время жизни и все, что касается гуйни. а класс просто дергает нужные ниточки класса-контроллера

суть в том, что контролы тоже выделяются в свой «бункер», что дает уже абстракцию и от фреймворка
Дело в том, что GUIController придётся знать, что творится внутри у Silo (потому что именно исходя из этой информации он создаёт интерфейс). Следовательно, изменяя что-то внутри Silo, придётся менять и GUIController.
Например, я захотел уникодовый Text: следовательно поменялся не только Silo, но и GUIController::AddButton().
совсем не обязательно. пусть контроллер заботится только о жизни и смерти контрола. а бункер будет дергать через методы контроллера нужные ниточки
А откуда Контроллер знает какие именно контролы создавать?
сказать ему чтоб создал контрол и отдал его id, дабы управлять им его же (контроллера) методами
Подходит как методика разруливания времени существования контролов и нотификации об этом Окна. Если это единственное что вас смущало — тогда это и есть решение, да. )
статья интересная, причем не только тем кто пишет на плюсах. Как делфист говорю:)
Вот только инкапсуляция гуи в классе совсем не понравилась. Может имеет смысл делать отдельный класс, регулирующий гуй и классы передавали бы данные через единожды стандартизированный механизм? Тот же бункер, при смене фреймворка придется менять только класс-контроллер.
Инкапсулировать сами контролы — это та же самая проблема, от которой вы уходите в коде — снижение скорости рефакторинга при изменениях в архитектуре
например как то так (извиняюсь за стиль, на сях уже давненько не писал)

class GUIController
{
public:
void AddButton(char* Text, ClickHandler* PressHandler)
};

class Silo
{
public:
void InitializeGUI(GUIController *GController);
private:
GUIController *FGUIController;
};
Но этого мало, когда речь идёт о сложных механизмах. Все сложные алгоритмы я записываю на бумагу. Невозможно написать на С++ то, что не можешь описать простым русским языком. Когда описываешь что-то словами, мозг натыкается на подводные камни и нестыковки. Они становятся видны в самом начале, а не в конце, когда код уже написан. Сам текст должен быть отшлифован, тонкие места — упомянуты. Ещё плюс: можно вернуться к проекту через год и не тратить недели на вспоминание почему это сделано именно так.


Автор неявно пропагандирует «литературное программирование», как я понимаю. Чтобы прийти к тому о чем говорит Кнут нужно 10 лет практики, не меньше.
Сложилось ощущение, что вы из тех, кто в методе OnClick выполняет запрос к базе данных.
Каждый объект — сам себе жнец, швец, на дуде игрец.

Довольно спорное утверждение. И хотя уже на эту тему не мало было высказываний добавлю: это явное противоречие одному из принципов ООП дизайна — Single responsibility principle. Я ни в коем случае не предлагаю слепо следовать утверждениям парням из книжек (тот же Robert C. Martin, который выделил этот принцип, в одной из своих презентаций говорит что самый лучший метод состоит из одной строки :-)). Но на мой взгляд Single responsibility principle как раз-таки очень правильный подход к проектированию системы классов. Не нужно бояться иметь много классов и много файлов. Как раз наоборот, если каждый класс имеет делает свою специфическую работу, то правильно назвав его вы как раз упростите систему. Также это позволяет строить более простые иерархии.

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

Статью как вводную для новичков не советовал бы. Уж больно спорные утверждения
Single responsibility principle, на самом деле, звучит довольно разумно. И как бы напоминает апологетам строгой инкапсуляции вроде меня, что фанатизм плох в любом деле. )
С одной стороны, создавать излишние сцепления я лично считаю порочной практикой. С другой стороны, когда сложность объекта превышает некую субъективную отметку, функционал объекта следует разбивать на части. И в этом случае я лично использую объекты-помощники, которые включаю в свой класс. Разумеется, сами объекты-помощники также закрыты, как и их владелец. И взаимодействие точно также идёт только через открытые функции-члены.
Разумеется, я не претендую на Истину. И с самого начала говорю про субъективность моего подхода. Утверждаю лишь, что всё это неплохо срабатывает на практике. Поскольку я писал и с открытыми объектами, и со строгой инкапсуляцией, смею считать что мне есть с чем сравнивать. Чего и другим желаю.

По поводу ТДД человек сказал лучше меня: habrahabr.ru/blogs/cpp/111120/#comment_3543916
По поводу ТДД человек и ответил лучше меня :-)

В любом случае поздравляю! Резонанс Ваша статья вызвала неплохой. Читать комменты очень интересно :-)
> Удержать в голове всю картину на всех уровнях абстракции одновременно, невозможно. Поэтому я делю сложную систему на куски попроще. Каждый из них хорошо понятен и очевиден, как гвоздь. Всё что сложнее, собирается из более простых элементов. Очевидно? Конечно! Но выполнять это правило нужно неукоснительно.

Нас в институте заставляли каждую задачу изображать вначале черным ящиком/сферой, а потом делить на подкомпоненты до достижения примитивов. Тоже и с блоксхемами алгоритмов — сначала общая задача, потом делится на подзадачи (подалгоритмы) и так до примитивов. В графике оно как-то даже нагляднее, видишь сколько уровней абстракции у тебя и т.п. Правда это все было для ассемблера и микроконтроллеров. Но нормы нисходящего и восходящего проектирования применимы везде, имхо.
Продолжение обязательно будет, черновик активно редактируется. Осталось доделать один документ по основной работе, после чего в кратчайшие сроки доделаю статью и выложу на обозрение. Думаю это будет в течение недели-двух.
Круто, будем ждать. Эта статья мне понравилась, надеюсь следующая тоже понравится :)
Only those users with full accounts are able to leave comments. Log in, please.