(no subject)
Oct. 8th, 2014 11:28 am![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
В С++ есть две "культуры" конструирования функций как объектов.
С одной стороны - функторы, тип которых отражает практически полностью их начинку. Т.е. если внутри этой функции будет сложение, то в навороченном объекте, функцию эту имплементирующем, а так же в его типе, будет какой-то подобъект "add", реализующий сложение, собственно сам объект представляет собой дерево более элементарных объектов, исполняющих математические действия, bind-ящих константы вместо каких-то отдельных аргументов итд., а его темплейтный тип имеет в качестве аргументов другие темплейтные типы итд. опять же деревом, и среди этих типов будет и add, и bind, и всё остальное прочее что мы по дороге в наш функтор запихали. И, что важно, функция вызова подобного функционального объекта не виртуальна. Подобные функции стали делать в STL, продолжили делать в boost и даже лямбда-функция в С++ в какой-то отдаленной мере отражает этот же подход, поскольку её тип уникален.
С другой стороны, построив с грехом пополам в С++ функтор как композицию объектов, или же создав лямбду, его или её можно присвоить инстансу класса std::function, который в своих темплейтных параметрах несет только сигнатуру необходимого функционального вызова, в отношении всей же остальной начинки выполняет типичный type erasure. И далее работать уже с std::function. Или же можно работать с функциональными объектами, которые имеют виртуальную функцию вызова (не как в STL или boost), тем самым осуществляя type erasure посредством обращения с классом функции через её базовый интерфейс.
Первый подход, очевидно, вызывает в С++ генерацию нового кода для каждой функции ИСПОЛЬЗУЮЩЕЙ наш функциональный объект, использование же класса "function" позволяет это избежать.
Мне интересно - какие есть принципиальные случаи, когда первый подход в принципе не работает? Один случай я уже знаю - это когда мы динамически генерим функтор по каким-то входным данным, скажем, пользователь может написать выражение y=sin(2*x), мы это выражение распарсиваем и строим функциональный объект, вычисляющий данное выражение. А вот если нет у нас юзеровского ввода, могут ли тем не менее быть ситуации, когда вариант с полной статической типизацией функтора в С++ не срабатывает? Ну, наверное, еще какая-нибудь задача, где нам надо сгенерить и перебрать какой-нибудь класс арифметических выражений.
Что-нибудь ещё менее вычурное из задач, где статическая типизация не подходит, имеется ли?
С одной стороны - функторы, тип которых отражает практически полностью их начинку. Т.е. если внутри этой функции будет сложение, то в навороченном объекте, функцию эту имплементирующем, а так же в его типе, будет какой-то подобъект "add", реализующий сложение, собственно сам объект представляет собой дерево более элементарных объектов, исполняющих математические действия, bind-ящих константы вместо каких-то отдельных аргументов итд., а его темплейтный тип имеет в качестве аргументов другие темплейтные типы итд. опять же деревом, и среди этих типов будет и add, и bind, и всё остальное прочее что мы по дороге в наш функтор запихали. И, что важно, функция вызова подобного функционального объекта не виртуальна. Подобные функции стали делать в STL, продолжили делать в boost и даже лямбда-функция в С++ в какой-то отдаленной мере отражает этот же подход, поскольку её тип уникален.
С другой стороны, построив с грехом пополам в С++ функтор как композицию объектов, или же создав лямбду, его или её можно присвоить инстансу класса std::function, который в своих темплейтных параметрах несет только сигнатуру необходимого функционального вызова, в отношении всей же остальной начинки выполняет типичный type erasure. И далее работать уже с std::function. Или же можно работать с функциональными объектами, которые имеют виртуальную функцию вызова (не как в STL или boost), тем самым осуществляя type erasure посредством обращения с классом функции через её базовый интерфейс.
Первый подход, очевидно, вызывает в С++ генерацию нового кода для каждой функции ИСПОЛЬЗУЮЩЕЙ наш функциональный объект, использование же класса "function" позволяет это избежать.
Мне интересно - какие есть принципиальные случаи, когда первый подход в принципе не работает? Один случай я уже знаю - это когда мы динамически генерим функтор по каким-то входным данным, скажем, пользователь может написать выражение y=sin(2*x), мы это выражение распарсиваем и строим функциональный объект, вычисляющий данное выражение. А вот если нет у нас юзеровского ввода, могут ли тем не менее быть ситуации, когда вариант с полной статической типизацией функтора в С++ не срабатывает? Ну, наверное, еще какая-нибудь задача, где нам надо сгенерить и перебрать какой-нибудь класс арифметических выражений.
Что-нибудь ещё менее вычурное из задач, где статическая типизация не подходит, имеется ли?
no subject
Date: 2014-10-08 04:38 pm (UTC)Так вот, первые пару лет я наигрался с этими методологиями и у меня появилось подозрение, что они снижают и производительность программиста, и качество программ, так как смещают фокус программиста с решения проблемы на программирование ради программирования, а также рутинно вводят лишние сущности, сильно затрудняющие поддержку кода после исчезновения исходных авторов. После смены 2-3 поколений разработчиков объектно-ориентированный с извращениями код превращается в неподдерживаемый хаос, а новые правки приводят к новым багам.
no subject
Date: 2014-10-08 04:55 pm (UTC)Мне доводилось видеть действительно страшные результаты применения ОО методологии и дизайна, и понятно, что чем проще язык программирования, тем потенциально проще разобраться в коде стиля "спагетти" на нём написанном, но учитывая, что я видел достаточно хорошо исполненные крупные проекты на С++ с существенным и принципиальным ОО дизайном, эта методология меня достаточно устраивает и я её ценю. Было и наличествует в этой области безбрежный фанатизм и уверенность, что ООП - непререкаемая и окончательная истина, но это уже вопрос отдельный. Отдельный же вопрос, хорош ли С++ как язык (нет, не хорош).
no subject
Date: 2014-10-08 05:06 pm (UTC)есть такое дело.
* а также рутинно вводят лишние сущности, сильно затрудняющие поддержку кода после исчезновения исходных авторов
и это верно.
А ещё верно, что проще ходить ногами, чем ездить по дороге, при этом соблюдая правила дорожного движения и занимаясь обслуживанием машины. Все эти "методологии" включают определенные накладные расходы и имеют определенную цену (что особо не рекламируется). Предполагается, что суммарный баланс расходов на решение инфраструктурных задач, создание всякой мишуры абстракций и проч проч и доходов, которые всё это принесёт, будет существенно положительный. Но понятно, что это верно отнюдь не для всякой задачи, и что для любого уровня полезности технологии найдется идиот, который обратит её во вред или которому эта методология необратимо покорёжит мозги.
no subject
Date: 2014-10-08 07:09 pm (UTC)Все это накрывается медным тазом после появления в проекте первых fresh college graduates без индустриального опыта, от которых менеджмент требует быстренько поставить заплату, чтобы программа прошла regression и ее можно было бы отправить клиенту. Учитывая, что люди с первыми или не особенно нужными им работами в гробу видали попытку разобраться в design intentions исходных авторов, думая о скором нахождении другой работы, они создают хаос, который (в сложных методологиях) приводит очень серьезной ненадежности.
В условиях, когда большие проекты зависят от взаимозаменяемых и вообще говоря бессовесных масс людей, лучше делать скелет проекта простым, который можно быстро понять даже человеку, который понимать особенно не старается.
no subject
Date: 2014-10-09 01:23 pm (UTC)Но, "простота" - понятие не однозначное, для первокурсника проще простого вычислить какой-то школьный определенный интеграл, но кто-то может отметить, что если бы первокурсник вычислял интеграл численно методом трапеций, то такой подход был бы более понятен пятикласснику. Мне случалось использовать столь "сложные" абстракции, как вынос общей функциональности в отдельную функцию, состоящей из 10 строк, с весьма ясным интерфейсом и очевидной функциональностью и - и натыкаться на непонимание такого подхода от людей (совсем не fresh graduates), которым ожидали от меня, что я десять раз напишу один и тот же фрагмент кода, как привыкли работать они. Наконец, кто-то может заметить, что оператор цикла есть некоторое усложнение кода по сравнению с использованием условного оператора -- начинающий программист оператора цикла может и не знать ))).
Я согласен, что если имеется менеджмент, дающий задания не подходящим для этого исполнителям и не организующий ревизии сделанной работы, то это проблема, часть решения которой может быть определенное упрощение кода в сторону его большей доступности для понимания неподготовленным и неквалифицированным человеком. Но это не единственное возможное решение, альтернатива - уволить плохих менеджеров, не нанимать людей без опыта в сложные проекты, где для них невозможно выделить доступную им работу, и уж тем более, не давать им делать ответственную работу без проверки (для чего опять же нужно увольнять плохих менеджеров). Разумеется, в реальности плохих менеджеров не всегда возможно уволить, и тут мы получаем замечательную закономерность: если фирма использует нетривиальные методологии, то это скорее всего означает, что она подбирает в основном качественных квалифицированных работников и умеет организовывать работу менее квалифицированных, ну а если она подбирает всякую шушеру, не умеет и не желает организовать технически её работу, то флаг ей в руки, пусть использует максимально простые технологии, а для внешнего наблюдателя это будет сигнальчиком, что фирма технически слабовата.
Мне неоднократно случалось вводить объектно-ориентированные решения, которые не создавали, а решали проблемы, вплоть до проблем, когда сами разработчики "простого" кода элементарно тонули в нём, теряя возможность его поддерживать и расширять. Т.е. наверное, для какого-нибудь идиота мои решения были бы менее ясны, чем straightforward код, и, возможно, нашлись бы умные и усидчивые люди, способные всё же поддержать малоподдерживаемое спагетти из С++ кода, написанного в не-ОО стиле, но, повторюсь, решение ориентироваться на идиотов - не единственно возможное и не идеально, а умных и усидчивых людей не нашлось, нашелся я - не очень усидчивый, но который превратил нерасширяемый код в расширяемую инфраструктуру.