Это хороший шаблон: заменить длинную функцию серией лямбд?


14

Я недавно столкнулся со следующей ситуацией.

class A{
public:
    void calculate(T inputs);
}

Во-первых, Aпредставляет объект в физическом мире, что является веским аргументом для того, чтобы не разделять класс. Теперь calculate()оказывается довольно долгая и сложная функция. Я воспринимаю три возможных структуры для этого:

  • напишите это как стену текста - преимущества - вся информация в одном месте
  • писать privateслужебные функции в классе и использовать их в calculateтеле - недостатки - остальная часть класса не знает / не заботится / не понимает об этих методах
  • напишите calculateследующим образом:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }
    

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


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

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Все эти методы:

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

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

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


2
Как вы думаете, что использование лямбда-выражений даст вам то, что вызовы функций не будут?
Blrfl

1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.Конечно, Aпредставляет данные об объекте, который может существовать в физическом мире. Вы можете иметь экземпляр Aбез реального объекта и реальный объект без экземпляра A, поэтому рассматривать их как одно и то же бессмысленно.
Довал

@Blrfl, инкапсуляция - никто не calculate()будет знать об этих подфункциях .
Vorac

Если все эти расчеты имеют отношение к справедливости A, это доводит их до крайности.
Blrfl

1
«Во-первых, Aпредставляет объект в физическом мире, что является веским аргументом для того, чтобы не разделять класс». Мне, к сожалению, сказали об этом, когда я начал программировать. Мне потребовались годы, чтобы понять, что это хоккей с лошадью. Это ужасная причина для группировки вещей. Я не могу сформулировать, что является вескими причинами для группировки вещей (по крайней мере, к моему удовлетворению), но это та, которую вы должны отказаться прямо сейчас. В конечном итоге, будь весь «хороший код», это то, что он работает правильно, относительно прост для понимания и относительно легко изменить (то есть изменения не имеют странных побочных эффектов).
jpmc26

Ответы:


13

Нет, это не очень хорошая модель

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

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

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

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

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

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

  • Функции, размещенные локально в потоке исходного кода, будут работать точно так же, не вызывая лямбду. Опять же, все, что имеет значение, - это то, что читатель может это прочитать
  • Комментарии вверху функции говорят: «мы делим эту функцию на три части», за которыми следуют большие длинные строки // ------------------между каждой частью.
  • Вы также можете поместить каждую часть расчета в свою собственную область. Это дает преимущество немедленного доказательства, вне всяких сомнений, что нет разделения переменных между частями.

РЕДАКТИРОВАТЬ: После просмотра вашего редактирования с примером кода, я склоняюсь к тому, что примечание комментариев является наиболее чистым, с скобками, чтобы обеспечить границы, предлагаемые комментариями. Однако, если какие-либо функции можно использовать в других функциях, я бы рекомендовал использовать функции вместо

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}

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

Отличный момент. Я рассматриваю это как несколько преимуществ подхода с лямбдами - (1) локально смежным кодом и с локальной областью (это будет потеряно функциями уровня файла) (2) компилятор гарантирует, что никакие локальные переменные не будут разделены между сегментами кода. Таким образом, эти преимущества могут быть сохранены путем разделения calculate()на {}блоки и объявления общих данных в области calculate()видимости. Я думал, что, увидев, что лямбды не захватывают, читатель не будет обременен силой лямбд.
Vorac

«Я думаю, что, увидев, что лямбды не захватывают, читатель не будет обременен силой лямбд». Это на самом деле является справедливым, но спорным утверждение, которое поражает в сердце лингвистике. Слова обычно имеют коннотации, которые выходят за рамки их значений. Является ли мой смысл lambdaнесправедливым, или вы грубо заставляете людей следовать строгому обозначению, ответить не так просто. На самом деле, это может быть совершенно приемлемо для вас, чтобы использовать lambdaэтот способ в вашей компании, и совершенно неприемлемо для моей компании, и ни один из них на самом деле не должен ошибаться!
Cort Ammon

Мое мнение о значении лямбды связано с тем, что я вырос на C ++ 03, а не на C + 11. Я потратил годы, развивая понимание конкретных мест, где С ++ страдает от недостатка lambda, таких как for_eachфункция. Соответственно, когда я вижу, lambdaчто не подходит ни к одному из тех легко обнаруживаемых случаев проблем, первое, к чему я прихожу, - это то, что он, вероятно, будет использоваться для функционального программирования, потому что в противном случае он не был необходим. Для многих разработчиков функциональное программирование - это совершенно другое мышление, чем процедурное или ОО-программирование.
Cort Ammon

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

20

Я думаю, что вы сделали неверное предположение:

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

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

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

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

То, что вы написали, немного напоминает объекты вложенных функций в стиле Javascript. Что снова является признаком того, что они тесно связаны друг с другом. Вы уверены, что вы не должны делать отдельный класс для них?

Подводя итог, я не думаю, что это хороший шаблон.

ОБНОВИТЬ

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


Звучит разумно, но мне сложно представить реализацию. Класс области файла со статическими методами? Класс, определенный внутри A(может быть, даже функтор со всеми другими методами private)? Класс объявлен и определен внутри calculate()(это очень похоже на мой лямбда-пример). Как пояснение, calculate()это один из семейства методов ( calculate_1(), calculate_2()и т. Д.), Все из которых просты, только этот - 2 экрана формул.
Vorac

@Vorac: Почему calculate()это намного дольше, чем все другие методы?
Кевин

@Vorac Трудно помочь, не видя твой код. Вы также можете опубликовать это?
Габор Angyal

@ Кевин, формулы для всего приведены в требованиях. Код размещен.
Vorac

4
Я бы проголосовал за это 100 раз, если бы мог. «Моделирование объектов в реальном мире» - начало смертельной спирали дизайна объектов. Это гигантский красный флаг в любом коде.
Фред Волшебная собака Чудес
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.