Pull to refresh

Comments 53

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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


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


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


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


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


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


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


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


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


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

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

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

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

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

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

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

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

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

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

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

Здесь


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

Лично я хочу, чтобы мои студенты не видели принципиальной разницы между x.foo() и foo(x), а использовали то, что удобнее здесь и сейчас.

Sign up to leave a comment.

Articles