2008-11-27 1 views
1

Предположим, что довольно большая библиотека шаблонов содержит около 100 файлов, содержащих около 100 шаблонов с общим количеством более 200 000 строк кода. Некоторые из шаблонов используют множественное наследование, чтобы сделать использование самой библиотеки довольно простой (т. Е. Наследовать некоторые базовые шаблоны и выполнять только определенные бизнес-правила).Как лучше всего перейти от шаблона беспорядок к чистой архитектуре классов (C++)?

Все, что существует (выращено в течение нескольких лет), «работает» и используется для проектов.

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

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

Есть ли проверенный способ решить эту задачу? Что было бы хорошим местом для начала?

ответ

13

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

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

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

Удаление шаблонов приведет только к зависимостей, не меньше. Добавленная безопасность типов шаблонов может быть использована для обнаружения множества ошибок во время компиляции (с этой целью сбрасывайте свой код с помощью static_assert)

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

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

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

+0

Хорошие баллы! Спасибо, что заставил меня думать в другом направлении. – 2008-11-27 17:22:40

2

Ну, проблема в том, что шаблонный способ мышления сильно отличается от объектно-ориентированного метода наследования. Трудно ответить на что-либо еще, кроме «переделать все это и начать с нуля».

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

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

+0

Я согласен, это звучит, как настоящая проблема - это не шаблоны, а плохой дизайн. – 2008-11-27 16:52:47

7

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

Кроме этого, "разделяй и conqueor" тесты

3

Запись единицы.

Где новый код должен делать то же, что и старый код.

Это один совет, по крайней мере.

Edit:

Если вы принизить старый код, который вы заменили с новой функциональностью вы могут поэтапную на новый код понемногу.

0

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

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

2

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


Lookup статических интерфейсам. Где шаблоны зависят от того, какие функции существуют? Где они нуждаются в typedefs?

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

Поиск целых списков. Если вы обнаружите, что ваш код использует целые списки, такие как list<1, 3, 3, 1, 3>, их можно заменить на std::vector, если все используемые ими коды могут работать с работами со значениями времени выполнения вместо постоянных выражений.

Тип поиска типа. Существует много кода, в котором используется проверка наличия определенного typedef или существует ли какой-либо метод в типичном шаблоном. Абстрактные базовые очки решают эти два вопроса, используя чистые виртуальные методы и наследуя typedefs к базе. Часто typedefs нужны только для того, чтобы вызвать отвратительные функции, такие как SFINAE, что также было бы излишним.

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

Объекты функции поиска. Если вы обнаружите, что ваш код использует объекты функции, вы можете изменить их, чтобы использовать абстрактные базовые классы, и иметь что-то вроде void run();, чтобы позвонить им (или если вы хотите продолжать использовать operator(), лучше так! Это тоже может быть виртуальным).

0

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

Еще одна вещь, которую вы можете рассмотреть, - это то, как часто вы очищаете иерархии наследования, создавая стиль «mixin» шаблонов вместо агрегатов множественного наследования. Посмотрите, сколько мест вы можете уйти, сделав один из аргументов шаблона именем базового класса, из которого он должен быть получен (работает boost::enable_shared_from_this). Конечно, это обычно работает хорошо, если конструкторы не принимают никаких аргументов, так как вам не нужно беспокоиться о правильности инициализации.

1

Насколько я понимаю, вас больше всего интересуют времена сборки и ремонтопригодность вашей библиотеки?

Во-первых, не пытайтесь «исправить» все сразу.

Во-вторых, поймите, что вы исправите. Частота шаблона часто возникает по причине, например. для обеспечения определенного использования и заставить компилятор помочь вам не ошибиться. Эту причину иногда можно отнести далеко, но выкидывать 100 строк, потому что «никто действительно не знает, что они делают» не следует воспринимать легкомысленно. Все, что я предлагаю здесь, может ввести очень неприятные ошибки, вас предупредили.

В-третьих, сначала рассмотрите более дешевые исправления: например. более быстрые машины или распределенные инструменты сборки. По крайней мере, выбросьте всю RAM, которую возьмут доски, и выбросите старые диски. Это различие. Один диск для ОС, один диск для сборки - дешевый RAID.

Является ли библиотека хорошо документированной? Это ваш лучший шанс сделать это. Посмотрите на такие инструменты, как doxygen, которые помогут вам создать такую ​​документацию.

Все считается? Хорошо, теперь некоторые предложения по времени сборки;)


Поймите build model C++: каждый .cpp составляется индивидуально. Это означает, что многие .cpp-файлы со многими заголовками = огромная сборка. Это НЕ советую поместить все в один .cpp-файл, хотя! Однако один трюк (!), Который может ускорить сборку, - это создать один .cpp-файл, который включает в себя кучу .cpp-файлов и только передать этот «главный» файл компилятору. Вы не можете сделать это слепо, однако - вам нужно понять, какие ошибки могут возникнуть.

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

Используйте предварительно скомпилированные заголовки. см. выше)

Проверьте политику включения заголовка. Хотя каждый файл должен быть «независимым» (т. Е. Включать все, что он должен быть включен кем-то другим), не включайте в себя либерально. К сожалению, я еще не нашел инструмент для поиска ненужных операторов #incldue, но это может помочь потратить некоторое время на удаление неиспользуемых заголовков в файлах «hotspot».

Создайте и используйте форвардные объявления для шаблонов, которые вы используете. Часто вы можете включать заголовок с объявлениями forwad во многих местах и ​​использовать полный заголовок только в нескольких конкретных. Это может значительно облегчить компиляцию. Проверьте заголовок <iosfwd>, как это делает стандартная библиотека для потоков ввода-вывода.

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

// .h 
template <typename FLOAT> // float or double only 
FLOAT CalcIt(int len, FLOAT * values) { ... } 

Вы можете объявить перегрузки в заголовке, и движение шаблон для тела:

// .h 
float CalcIt(int len, float * values); 
double CalcIt(int len, double * values); 

// .cpp 
template <typename FLOAT> // float or double only 
FLOAT CalcItT(int len, FLOAT * values) { ... } 

float CalcIt(int len, float * values) { return CalcItT(len, values); } 
double CalcIt(int len, double * values) { return CalcItT(len, values); } 

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

Проверьте, может ли код the PIMPL idiom переместить код из заголовков в файлы .cpp.

Общее правило, которое скрывается за этим - , отделяет интерфейс вашей библиотеки от реализации. Используйте комментарии, detail namesapces и отдельные заголовки .impl.h, чтобы мысленно и физически изолировать то, что должно быть известно снаружи, от того, как это делается. Это раскрывает реальную ценность вашей библиотеки (действительно ли она инкапсулирует сложность?) И дает вам возможность сначала заменить «простые цели».


Более конкретный совет - и насколько полезен данный данный параметр - во многом зависит от фактической библиотеки.

Удачи вам!

Смежные вопросы