Подтвердить что ты не робот

Что вы чувствуете, чрезмерное обобщение?

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

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

В последнее время мне очень понравилось такое консервативное отношение:

Вы никогда не должны делать ничего более общего, чем необходимо, - основное правило разработки программного обеспечения.

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

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

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

Так что ты думаешь, ТАК? Что вы считаете чрезмерно обобщающим? Имеет ли это правило различную применимость в зависимости от контекста? Вы даже согласны, что это правило?

4b9b3361

Ответ 1

Более обобщение делает меня сумасшедшим. Я не боюсь шаблонов (нигде рядом), и мне нравятся общие решения. Но мне также нравится решать проблему, за которую клиент платит. Если это недельный проект, то почему я финансирую феерию на один месяц, которая будет продолжать работать не только через очевидные возможные будущие изменения, как новые налоги, но, вероятно, благодаря открытию новых спутников или жизни на Марсе?

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

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

Ответ 2

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

Существует принцип, по которому люди XP следуют под названием YAGNI - вам это не понадобится.

wiki говорит следующее:

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

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

Ответ 3

Слишком общий? Я должен признать, что я поклонник Generic Programming (как принцип), и мне очень нравится идея, что Haskell и Go используют там.

Однако при программировании на С++ вам предлагаются два способа достижения схожих целей:

  • Общее программирование: путем шаблонов, даже если есть проблемы со временем компиляции, зависимость от реализации и т.д.
  • Объектно-ориентированное программирование: его предок таким образом, который помещает проблему на самом объекте (класс/struct), а не на функцию...

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

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

// No container implements this, it easy... but better write it only once!
template <class Container, class Pred>
void erase_if(Container& c, Pred p)
{
  c.erase(std::remove_if(c.begin(), c.end(), p), c.end());
}

// Same as STL algo, but with precondition validation in debug mode
template <class Container, class Iterator = typename Container::iterator>
Iterator lower_bound(Container& c, typename Container::value_type const& v)
{
  ASSERT(is_sorted(c));
  return std::lower_bound(c.begin(), c.end(), v);
}

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

Вот почему я сам ценю принцип наименьших усилий. Когда я думаю о классе или методе, я сначала делаю шаг назад и немного думаю:

  • Будет ли смысл иметь более общий характер?
  • Какова будет стоимость?

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

Пример:

void Foo::print() { std::cout << /* some stuff */ << '\n'; }

// VS

std::ostream& operator<<(std::ostream& out, Foo const& foo)
{
  return out << /* some stuff */ << '\n';
}

Не только более общий (я могу указать, где выводить), но и более идиоматично.

Ответ 4

Что-то чрезмерно обобщается, когда вы теряете время, обобщая его. Если вы собираетесь использовать общие функции в будущем, вы, вероятно, не будете тратить время. Это действительно так просто [на мой взгляд].

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

Ответ 5

Я думаю, вы должны рассмотреть два основных принципа программирования: KISS (держите его простым и понятным) и DRY (не повторяйте себя). Большую часть времени я начинаю с первого: реализовать необходимую функциональность наиболее простым и простым способом. Довольно часто это достаточно, потому что оно уже может удовлетворить мои требования. В этом случае он остается простым (и не общим).

Когда второе (или максимальное третье) время мне нужно что-то подобное, я пытаюсь обобщить проблему (функцию, класс, дизайн, что угодно) на основе конкретных примеров реальной жизни → маловероятно, чтобы Я делаю обобщение только для себя. Следующая аналогичная проблема: если она подходит к текущей картине элегантно, прекрасно, я могу легко ее решить. Если нет, я проверю, может ли текущее решение быть дополнительно обобщено (не делая его слишком сложным/не очень элегантным).

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

Однако для этого могут быть некоторые исключительные случаи. a) Когда общее решение почти точно такое же усилие и сложность. Пример: запись реализации очереди с использованием дженериков не намного сложнее, чем то же, что и для строк.
б) Если проще решить проблему в общем виде, а также решение легче понять. Это происходит не слишком часто, я не могу придумать простой пример реальной жизни на данный момент:-( Но даже в этом случае использование/анализ конкретных примеров ранее было обязательным IMO, поскольку только это может подтвердить что вы на правильном пути.

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

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

И, конечно же, различные гибкие процессы также рекомендуют нечто подобное: начните с простого, рефакторинга, когда это необходимо.

Ответ 6

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

Object saveOrUpdate(Object object)

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

Ответ 7

есть 2 примера из Microsoft в чрезмерном обобщении:
1.) CObject (MFC)
2.) Объект (.Net)

оба они используются для "реализации" генериков в С++, которые большинство людей не использует. Фактически, все делали проверку на параметр, заданный с использованием этих (CObject/Object) ~