Конкретные функции против множества аргументов против зависимости от контекста

Пример

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

Реализация конкретного случая (слишком много функций)

writeInUpperCaseAndCentered(char *str){//..}
writeInLowerCaseAndCentered(char *str){//..}
writeInUpperCaseAndLeft(char *str){//..}
and so on...

vs

Функция многих аргументов (плохая читабельность и даже сложность кодирования без хорошей IDE с автодополнением)

write( char *str , int toUpper, int centered ){//..}

vs

Зависимость от контекста (трудно повторно использовать, сложно кодировать, использовать уродливые глобальные переменные, а иногда даже невозможно «обнаружить» контекст)

writeComplex (char *str)
{    
    // analize str and perhaps some global variables and 
    // (under who knows what rules) put it center/left/right and upper/lowercase
}

И, возможно, есть другие варианты... (и приветствуются)

Вопрос в том:

Есть ли какая-либо хорошая практика или опыт/академический совет для этой (повторяющейся) трилеммы?

РЕДАКТИРОВАТЬ:

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


person Hernán Eche    schedule 01.07.2010    source источник
comment
Я не вижу, чтобы ООП помог решить эту проблему: ваш первый подход эквивалентен подклассу каждого случая, второй — использованию множественного наследования, третий — написанию объекта умного бога, а лучшие варианты, предложенные ниже, — использованию вспомогательного объекта WriterConfig. . ООП может навязывать хорошие организационные методы и может быть большим выигрышем в ремонтопригодности, но это не волшебство.   -  person dmckee --- ex-moderator kitten    schedule 01.07.2010
comment
Существует отличное решение для ООП (по крайней мере, в java), взгляните на шаблон Builder, который появляется в Эффективной книге Java Джошуа Боша, informit.com/articles/article.aspx?p=1216151&seqNum=2 это не волшебство, но близко =P   -  person Hernán Eche    schedule 02.07.2010


Ответы (6)


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

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

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

#define WRITE_FORMAT_LEFT   1
#define WRITE_FORMAT_RIGHT  2
#define WRITE_FORMAT_CENTER 4
#define WRITE_FORMAT_BOLD   8
#define WRITE_FORMAT_ITALIC 16
....
write(char *string, unsigned int format)
{
  if (format & WRITE_FORMAT_LEFT)
  {
     // write left
  }

  ...
}

РЕДАКТИРОВАТЬ: Чтобы ответить Грегу С.

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

  1. Добавьте #define WRITE_FORMAT_UNDERLINE 32 в заголовок
  2. Добавьте поддержку подчеркивания в write().

В этот момент он может вызывать write(..., ... | WRITE_FORMAT_UNLDERINE) где угодно. Более того, мне не нужно изменять уже существующие вызовы для записи, что мне пришлось бы сделать, если бы я добавил параметр в его сигнатуру.

Другое потенциальное преимущество заключается в том, что он позволяет вам делать что-то вроде следующего:

#define WRITE_ALERT_FORMAT  (WRITE_FORMAT_CENTER | \
                             WRITE_FORMAT_BOLD |   \
                             WRITE_FORMAT_ITALIC)
person torak    schedule 01.07.2010
comment
да, одна глобальная переменная состояния с битовыми флагами, я иногда использовал это. - person Hernán Eche; 01.07.2010
comment
Я не понимаю, чем использование битовых флагов отличается от использования какой-либо другой формы аргументов. О каких именно преимуществах вы говорите? - person Greg S; 01.07.2010
comment
@Greg S: Хороший вопрос, добавляющий объяснение к ответу. - person torak; 01.07.2010
comment
@Greg @torak Это точно так же, как использование структуры, но битовые флаги полезны при работе со встроенными микроконтроллерами, потому что использование памяти, конечно, ограничено данными, например, unsigned int может быть 16-битным или 32-битным, и это всего да- нет флагов, очень ограничено - person Hernán Eche; 01.07.2010
comment
Это также традиционный подход, и его можно заставить работать с небинарными опционами, если они принимают только несколько возможных значений (просто закодируйте их в более чем один бит). - person dmckee --- ex-moderator kitten; 01.07.2010

Я предпочитаю аргументацию.

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

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

typedef struct {
    int toUpper;
    int centered;
    // etc...
} cases;
write( char *str , cases c ){//..}
person Luca Matteis    schedule 01.07.2010
comment
не могли бы вы возразить в пользу аргументов? - person Hernán Eche; 01.07.2010
comment
Твердо... это лучший из всех миров, действительно, с низким количеством аргументов и не полагающимся на глобальные переменные. +1 - person Platinum Azure; 01.07.2010
comment
Немного погодя я понял, что это ОПАСНО. Когда вы добавляете новый тип форматирования, вам нужно найти все экземпляры, где вы вызываете запись, и преднамеренно установить значение для этого параметра в теперь более крупной структуре. Если вы этого не сделаете, настройка новой опции будет случайной. Кроме того, не будет ли неудобно определять и/или инициализировать структуру каждый раз, когда вы вызываете функцию записи? - person torak; 01.07.2010
comment
@torak: Сделайте структуру case непрозрачного типа — всегда инициализируйте, когда вы запрашиваете новый — в первую очередь, и вам нужно только исправить процедуру инициализации. Вы найдете этот подход в нескольких местах традиционного API Unix, и он хорошо работает. - person dmckee --- ex-moderator kitten; 01.07.2010
comment
@dmckee: Можете ли вы привести пример? Конечно, если структура непрозрачна, вы больше не можете получить к ней доступ, чтобы делать такие вещи, как установка/очистка toUpper? Это если нет набора функций, которые сделают это за вас. - person torak; 01.07.2010
comment
@torak: Функция Write, конечно, должна иметь доступ к внутренностям, но вы не раскрываете их в заголовке (т.е. сокрытие информации). Если вам нужна изменчивость, вы предоставляете функции, хотя люди часто предпочитают не разрешать это (сравните с практикой ООП, когда во многих случаях предпочтение отдается неизменяемым объектам). Стандартный дескриптор c FILE является примером такого виджета, как и структуры BSD glob_t и regex_t. В меньшей степени временная структура tm похожа на эту (вы знаете, что в ней находится, но не вы должны строить ее самостоятельно: вы спрашиваете API). - person dmckee --- ex-moderator kitten; 01.07.2010
comment
Чтобы расширить мой комментарий к самому вопросу: вы можете использовать все хорошие методы, поощряемые в ООП, при выполнении чисто процедурного программирования, у вас просто нет хорошего синтаксического сахара, чтобы обеспечить его соблюдение, и вы должны использовать self -дисциплина вместо этого. - person dmckee --- ex-moderator kitten; 01.07.2010
comment
@dcmkee: Конечно, все это правда. Однако, учитывая, что намерение состоит в том, чтобы использовать структуру для установки различных опций для различных вызовов write, накладные расходы (как в коде, так и в вычислениях) кажутся несколько чрезмерными. - person torak; 01.07.2010

Я бы выбрал комбинацию методов 1 и 2.

Закодируйте метод (A), который имеет все аргументы, которые вам нужны/которые вы можете придумать прямо сейчас, и «голую» версию (B) без дополнительных аргументов. Эта версия может вызывать первый метод со значениями по умолчанию. Если ваш язык поддерживает это, добавьте аргументы по умолчанию. Я бы также рекомендовал вам использовать осмысленные имена для ваших аргументов и, по возможности, перечисления, а не магические числа или серию флагов true/false. Это значительно упростит чтение вашего кода и того, какие значения на самом деле передаются, без необходимости искать определение метода.

Это дает вам ограниченный набор методов для поддержки, и 90% ваших применений будут базовым методом.

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

person ChrisF    schedule 01.07.2010

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

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

Format title = {upper_case, centered, bold};
Format body = {lower_case, left, normal};

write(title, "This is the title");
write(body, "This is some plain text");

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

person Jerry Coffin    schedule 01.07.2010
comment
Стандарт C утверждает, что у него есть объекты. Тот факт, что в данном случае рассматриваемые объекты создаются с помощью ключевого слова struct, ничуть не меняет идею. - person Jerry Coffin; 01.07.2010
comment
@MikeD: Для нас старый объект процедурных программистов имеет более широкое применение: он может означать структуру данных, закодированную в struct. Важно то, что это логическая единица в программе. - person dmckee --- ex-moderator kitten; 01.07.2010

Как вы уже упоминали, одним поразительным моментом является удобочитаемость: writeInUpperCaseAndCentered("Foobar!") гораздо легче понять, чем write("Foobar!", true, true), хотя вы можете устранить эту проблему, используя перечисления. С другой стороны, наличие аргументов позволяет избежать неудобных конструкций, таких как:

if(foo)
  writeInUpperCaseAndCentered("Foobar!");
else if(bar)
  writeInLowerCaseAndCentered("Foobar!");
else
 ...

По моему скромному мнению, это очень сильный аргумент (без каламбура) в пользу аргументации.

person Greg S    schedule 01.07.2010

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

person stinky472    schedule 01.07.2010