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

Начало

Довольно часто у студентов, изучающих C++ в определённых учебных кругах, складывается мировоззрение о том, что всё должно быть объектами. Попросите их написать программу, которая считает некоторое значение - и они начнут с создания объекта ValueComputer и метода vc.computeResult().

Например: дана задача с помощью динамического программирования посчитать количество способов замостить костяшками домино прямоугольник w \times h. Студент пишет:

int main()
{
    DominoTilingCounter tc(4, 7); // in a 4x7 rectangle
    std::cout << tc.count() << '\n';
}

Обозначив задачу, студент далее приступает к написанию класса DominoTilingCounter. А если он смышлёный, то также добавит мемоизацию, чтобы метод count() не считал каждый раз всё заново:

class DominoTilingCounter {
    int height, width;
    bool done = false;
    int tilingCount;
    int computeCount(int h, int w, std::string_view prevRow, int rowIdx) {
        [...recursive solution omitted...]
    }
public:
    explicit DominoTilingCounter(int h, int w) : height(h), width(w) {
        if (h == 0 || w == 0 || (w*h) % 2 != 0) {
            tilingCount = 0;
            done = true;
        }
    }
    int count() {
        if (!done) {
            tilingCount = computeCount(height, width, "", 0);
            done = true;
        }
        return tilingCount;
    }
};

(Если вам от этого кода захотелось фыркнуть - хорошо. Так и задумано.)

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

Продолжение, или рассуждаем логически

Смотрите: ты конструируешь объект DominoTilingCounter tc, только чтобы вычислить tc.count(), правильно? И больше объект ни для чего не используется?

Ещё раз: ты конструируешь объект DominoTilingCounter tc, только чтобы вычислить tc.count().

Так перенеси вычисление в конструктор!

class DominoTilingCounter {
    int height, width;
    int tilingCount;
    int computeCount(int h, int w, std::string_view prevRow, int rowIdx) {
        [...recursive solution omitted...]
    }
public:
    explicit DominoTilingCounter(int h, int w) : height(h), width(w) {
        if (h == 0 || w == 0 || (w*h) % 2 != 0) {
            tilingCount = 0;
        } else {
            tilingCount = computeCount(height, width, "", 0);
        }
    }
    int count() const {
        return tilingCount;
    }
};

Вот что изменилось после рефакторинга:

  • метод count() стал константным,

  • объект больше не может находиться в "невычисленном" состоянии,

  • убрали поле done, которое использовалось, только чтобы отличать это "невычисленное" состояние (кстати, в C++17 можно было использовать std::optional для тех же целей, но нам теперь и не надо).

Более того, поля width и height тоже больше не нужны. Они использовались, чтобы передать данные из конструктора в метод count(), а теперь все вычисления происходят в конструкторе, ничего никуда передавать не нужно. Код значительно сократился.

Конец

Так получилось, что с самого начала метод computeCount() не обращался к полям height и width класса, а получал данные через параметры w и h. По счастливому случаю метод computeCount() не обращается вообще ни к каким полям класса, значит, его можно пометить как static. Теперь наш код выглядит как-то так:

class DominoTilingCounter {
    int tilingCount;
    static int computeCount(int h, int w, std::string_view prevRow, int rowIdx) {
        if (h == 0 || w == 0 || (w*h) % 2 != 0) {
            return 0;
        }
        [...recursive solution omitted...]
    }
public:
    explicit DominoTilingCounter(int h, int w) {
        tilingCount = computeCount(h, w, "", 0);
    }
    int count() const {
        return tilingCount;
    }
};

Заметим, что, по сути, теперь класс просто оборачивает int!

Избегайте классов, по своей сути равнозначных int.

(А что насчёт float или double? - прим. перев.)

Что нам нужно было написать, это простую свободную функцию countDominoTilings(h, w):

int countDominoTilingsImpl(int h, int w, std::string_view prevRow, int rowIdx) {
    if (h == 0 || w == 0 || (w*h) % 2 != 0) {
        return 0;
    }
    [...recursive solution omitted...]
}

int countDominoTilings(int h, int w) {
    return countDominoTilingsImpl(h, w, "", 0);
}

int main() {
    int tc = countDominoTilings(4, 7);  // in a 4x7 rectangle
    std::cout << tc << '\n';
}

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

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

Комментарии (52)


  1. kompilainenn2
    29.05.2022 15:24
    +24

    вот так сюрприз, оказывается для простого действия не нужно городить классы?


    1. GospodinKolhoznik
      30.05.2022 00:12
      +42

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


  1. nin-jin
    29.05.2022 15:36
    +15

    ты конструируешь объект DominoTilingCounter tc, только чтобы вычислить tc.count(), правильно?

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

    Можно забыть про мемоизацию (теперь это забота вызывающего кода). 

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


    1. aamonster
      29.05.2022 16:58
      -6

      Ну, если так рассуждать – то ещё и "для 10 классов кэширование пишется в 10 местах", и "по красоте" (single responsibility или как там) надо выносить обвязку-мемоизатор в отдельную сущность.

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


    1. Tangeman
      29.05.2022 17:21

      В приведённой задаче класс используется один раз, причём точно известно что это максимальное количество раз и точно известно где — в main(), т.е. нигде больше:


      Ещё раз: ты конструируешь объект DominoTilingCounter tc, только чтобы вычислить tc.count().


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


      1. nin-jin
        29.05.2022 18:27
        +15

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

        Хотя, нет, к чёрту реальный мир, давайте хейтить студента за то, что он написал переиспользуемое решение и подумал об узких местах, а не наговнякал всё в мейне. Его задача - научиться писать легаси код!


        1. vadimr
          29.05.2022 19:07
          +5

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

          Не надо пытаться решать несуществующие проблемы за тот же бюджет.

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


          1. nin-jin
            29.05.2022 19:56
            +2

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

            Про перенос вычислительных задач в субд я вообще молчу.


            1. vadimr
              29.05.2022 19:58
              +3

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

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


              1. nin-jin
                29.05.2022 21:34
                +10

                Берите выше - самое узкое место в мозгах. Когда мозгов нет, то люди экономят 20% времени в "нулевой месяц", а потом расхлёбывают последствия следующие 10 месяцев реализуя те же фичи за 200% времени. Вместо того, чтобы за 2 месяца сделать нормальную архитектуру, нормально оттестировать базовые абстракции, и не кранчить потом целый год заливая проект тоннами копипасты.

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

                А если у вашего финдиректора горизонт планирования в 1 месяц, то у меня для вас плохие новости - любой форс-мажор вас похоронит.


                1. vadimr
                  29.05.2022 21:56
                  +3

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

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

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

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


                  1. SIMPLicity
                    30.05.2022 08:22
                    +1

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

                    +1

                    Да, реальный мир - это ад,- а не так как учат добрые книжки и светлые учителя. Печеньки, увы, на тёмной стороне :(

                    Ну, и собственно, уже было тут...

                    PS Но мне всё-таки нравится то, что работает "само" и "почти вечно" ;)


                  1. starik-2005
                    30.05.2022 09:39
                    +1

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

                    ЗЫ: видел заказчиков, которые получали продукт, плевались и больше их не видели. Это куда правдоподобнее.


                    1. vadimr
                      30.05.2022 09:45

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

                      Будущее, может, и не бесконечно, но лет на 20 ему хватит. Не завидуйте :)


                      1. starik-2005
                        30.05.2022 09:54

                        Ну я как-то в университете еще делал для одной лабы научной матаппарат. Было в 96-99-м годах. Под ДОС. И тоже все были довольны, а потом ДОС умер, да и процессоры стыли быстрыми, поэтому crt-модуль давал division by zero. А у Вас так вот прям получилось-получилось.

                        Да, все в мире может быть, но нет ничего постояннее, чем временное. И в США на коболе до сих пор писанный софт работает, да так хорошо, что пришлось пенсионеров доставать, когда "внезапно" выросла нагрузка. Ну и т.д.


                      1. vadimr
                        30.05.2022 09:58

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


                      1. starik-2005
                        30.05.2022 10:15

                        Ну в таком ключе сложно что-то сделать "не так", не находите?


                      1. vadimr
                        30.05.2022 10:24

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


                      1. starik-2005
                        30.05.2022 10:29

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


                      1. vadimr
                        30.05.2022 10:35

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


                      1. starik-2005
                        30.05.2022 10:52

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


                      1. vadimr
                        30.05.2022 11:00

                        Задел всегда есть где взять. Вот как Тоёта – то добавит подогрев руля, то уберёт, но зато сделает складывающиеся зеркала, то ещё что-нибудь поменяет. А так, конечно – то, что нас не убивает, делает нас сильнее, и любая работа приносит опыт.


                  1. avdosev
                    30.05.2022 10:58
                    +5

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


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


                    1. sergegers
                      31.05.2022 13:19

                      Только этим мальчиком был Альберт Эйнштейн.


        1. Reposlav
          30.05.2022 15:03

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

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

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

          Все деньги заработает тот, кто первый выставит продукт на рынок, а не тот, кто заложит лучшую архитектуру


          1. nin-jin
            30.05.2022 16:20


            1. Reposlav
              30.05.2022 18:38

              В Mass Effect не играл, но наслышан. Боюсь, что захейтили его далеко не только за баги.

              Контр-пример: TES. Сколько уж там багов, а люди все равно всем сердцем любят эту серию.

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

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


              1. nin-jin
                30.05.2022 21:43

                Захейтили его за халтуру. Как и множество других на скорую руку слепленных проектов "шобы побыстрей, а там разберёмся". А TES делался с любовью.

                Apex Legends же вышел далеко не первым, но вылизанным, и всех порвал. При этом сделан он небольшой командой профессионалов в короткие сроки, основываясь на наработках TitanFall.


        1. Tangeman
          30.05.2022 18:58
          +2

          Добро пожаловать в реальный мир...

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


          Допустим, у вас в квартире дала течь батарея, горячая вода хлещет, вы вызываете сантехника а он вам говорит — "Мне нужно два-три дня, нужно всё посчитать, измерить, спроектировать непротекаемые заплатки и новые трубы, поставить сигнализацию на протечку… Нет, отключить воду на это время нельзя — другие клиенты нуждаются в отоплении" — представили?


          Так вот, "разработка" — это не только большие крупные проекты, которые пилятся месяцами и годами, это ещё и ad hoc решения которые нужны клиенту если не вчера, то в худшем случае завтра — причём буквально "завтра", а не "через недельку", хуже того, речь может идти о часах.


          Обычно это что-то сравнительно небольшое (ну может несколько сотен, иногда пара-тройка тысяч) строк для решения конкретной проблемы возникшей по ходу дела, типа быстренько набросать анализатор логов которые в странном формате (да-да, legacy которому лет 10), дабы найти там паттерны попыток DoS или чего-то типа, плюс по итогам анализа нужно всех найденных супостатов блочить с помощью фильтра который имеет свой проприетарный API для управления — давайте, скажите клиенту что вам нужно два месяца на "правильную" архитектуру, "правильное" проектирование и вот это всё, а пока пусть его инфрастуктура лежит и он теряет миллионы (евро, не рублей) в день (и репутацию) — зато потом у него будет универсальный супер-обучаемый и настраиваемый анализатор логов для приложения которое он выкинет через два месяца в связи с тем что оно морально устарело (по причине чего проблема и возникла, собственно).


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


          И что самое забавное, речь не о каких-то "тяп-ляп" конторах, которые хотят "дешево и быстро" — речь о вполне уважаемых больших фирмах с десятками тысяч сотрудников и миллиардными доходами, типа провайдеров мобильной связи. Да-да, можете не верить — у них в недрах тоже случается "форс-мажор" и их стандартные подрядчики (или собственный IT отдел) говорят "ну нет, нам полгода нужно только чтобы архитектуру разработать" — а задача вот она, горит — и тут выходим мы в заляпаных спецовках с немытыми руками, с "неправильным legacy кодом" который решает конкретную задачу ровно так как хочет заказчик и ровно тогда когда ему это нужно.


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


          Из всех моих проектов примерно четверть — именно такого плана, когда времени нет а ехать надо, задачи обычно подкидывают постоянные (а не "залётные") клиенты — и честно говоря, я вот не могу вспомнить ни одной из них которая повторялась бы (даже в пределах одного клиента) — что лишает всякого смысла тратить время на то чтобы "всё было идеально" (хотя хотелось бы, и вполне даже можется).


          И нет, "сделано на коленке" — это не всегда значит "ненадёжно", можно сделать надёжно и с плохо структурированным нечитаемым кодом (так быстрее) — если вы точно знаете что туда никто не полезет второй раз (а это почти всегда так в случае ad hoc).


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


          Так что, реальный мир он разный бывает — если клиенту нужен розовый слон с бантиками на банкет завтра вечером, проще (и разумней) арендовать серого слона, покрасить в розовый и прицепить бантики, чем строить R&D, выводить розовых слонов с ушами в форме бантиков, параллельно развивая логистику для их доставки, лечения и кормления. Да, если пойдёт дождь слон снова станет серым, и бантики оторвутся если их подёргать — но мы точно знаем что банкет под крышей, бантики никто не дёргает, да и вообще слон нужен всего на один вечер — и все довольны.


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


          1. nin-jin
            31.05.2022 10:35

            Форс-мажор на форс-мажоре и форс-мажором форс-мажористо форс-мажорит.
            Форс-мажор на форс-мажоре и форс-мажором форс-мажористо форс-мажорит.


    1. Sap_ru
      29.05.2022 18:46
      +3

      И класс тут не нужен. Тут статический метод (или нестатический если есть некоторый поставщик данных) нужен.
      Вот, когда класс будет нужен, тогда его и нужно делать.
      А иначе такой подход в лютую дичь выливается.
      Сегодня мы в конструкторе вычисления проводим, а завтра оказывается, что вычисления могут исключения выбрасывать, и на Хабре появляется ещё одна статья-открытие об исключениях в конструкторах C++.
      Сегодня мы рассказываем, что компилятор всё эту ересь заоптимизирует, а завтра пытаемся собрать код с "-O0" или даже "-O1" и тихо офигеваем от результата.
      А послезавтра мы выносим класс в отдельный файл/модуль (ну, нам же всё равно где он располагается?) и через год начинаем гордо рассказывать на конференциях о том, как не спав месяц сэкономили 100500 ресурсов и нашли bottle neck, оказавшийся используемым в миллионе мест вычислительным классом, который «почему-то» не «заоптимизировался». И дальше 40 минут про сайд-эффекты, ограничение оптимизации между модулями и про то, что «знает», а чего «не знает» компилятор.
      Нет цели хранить данные — класс не нужен.


    1. leotsarev
      30.05.2022 08:35

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

      Например, в C# такой класс Lazy<T> есть в стандартной библиотеке


      1. nin-jin
        30.05.2022 09:22
        +3

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


      1. iamkisly
        30.05.2022 23:09

        Какбы надо начать размышления с того, что в dotnet (уже не помню как дела обстоят в cpp) статическая функция main обернута в класс. Даже если мы не пишем это явно (например как в шаблонах dotnet6), он все равно есть.


        1. HackerDelphi
          31.05.2022 20:28

          Не в dotnet, а в C#, точности ради. MSIL вполне себе поддерживает метолы уровня модуля.


  1. aamonster
    29.05.2022 15:41
    +4

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


  1. oleg_shamshura
    29.05.2022 15:50
    +1

    Зачем explicit конструктору, у которого два аргумента?


    1. OldFisher
      29.05.2022 18:53
      +5

      На случай, если попадутся списки инициализации. Вот здесь https://en.cppreference.com/w/cpp/language/explicit в примерах показано применение и к чему оно приводит.


  1. fzn7
    29.05.2022 16:15
    +1

    В таких условиях и C++ не нужен


  1. impwx
    29.05.2022 16:30
    +6

    Довольно часто у студентов, изучающих C++ в определённых учебных кругах, складывается мировоззрение о том, что всё должно быть объектами.

    Где-то сейчас заплакал один Егор Бугаенко


    1. MentalBlood
      29.05.2022 19:14

      Здесь


      они начнут с создания объекта ValueComputer и метода vc.computeResult()

      он тоже заплакал, но по другой причине )


  1. Sap_ru
    29.05.2022 17:55

    Дичь какая-то. Нафиг такое решение.
    Функция в неймспейсе или статический метод класса — вот это будет правильным решением.


  1. Kotofay
    29.05.2022 18:38
    +1

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

    Если условий нет то и классы не нужны, создайте функтор или функцию в С-style.


  1. TheCalligrapher
    29.05.2022 19:36
    +5

    Демагогический прием, на основе которого построена эта статья, называется "ad absurdum". Но к чему это здесь?


    1. vadimr
      29.05.2022 20:30

      Да я бы не сказал, что ad absurdum. В реальной жизни довольно часто встречается искусственное оборачивание императивных алгоритмов в нефункциональные в конкретном случае объектно-ориентированные обёртки. Сама структура языков C++/Java этому способствует.

      А происходит так потому, что учат объектному стилю зачастую на куцых примерах, для которых объектный стиль (во всяком случае в его C++/Java трактовке) вовсе не нужен.

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


  1. AlexSpaizNet
    30.05.2022 00:07

    Эта статья это стеб?

    Не дай бог после таких советов студенты теперь все вычисления будут в конструктор пихать ????


  1. Refridgerator
    30.05.2022 07:59

    Если студент на курсе «си с классами» решает задачу в стиле ООП — то он всё делает правильно, потому как его задача и состоит в умении мыслить категориями ООП. И если рассмотреть в качестве «вычислятора» немного другую задачу — быстрое преобразование Фурье — то внезапно окажется, что прав был именно студент. Потому как у FFT есть таблицы, которые нужно где-то хранить и можно шарить частично или полностью, а в некоторых вариациях может понадобиться временный буфер, который тоже надо где-то хранить, а у этого буфера может быть увеличенная разрядность для уменьшения погрешности вычисления — и вот уже появляются функции ConvertFromDoubleToExtended и обратно, о которых пользователю знать совсем не интересно. var fft = new FFT(1024); fft.Transform(ref data); — именно так должен выглядеть ООП подход, а не куча статичных процедур.


    1. Refridgerator
      30.05.2022 11:19

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


  1. fishHook
    30.05.2022 10:58

    складывается мировоззрение о том, что всё должно быть объектами

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


    1. MentalBlood
      30.05.2022 11:28

      Вот после всего описанного и обычно приходит понимание, что


      все это вообще вкусовщина

      Поэтому хорошо когда оно вот так, что на одной паре одно, а на другой совсем другое говорят, иначе появится еще один влюбленный в лисп или апологет чистого кода )


      1. fishHook
        30.05.2022 12:44
        +1

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


  1. jrr_mota
    01.06.2022 09:57

    Если кратко: «You aren't gonna need it»