Типизация функций - зло или добро?
Это продолжение размышлений из позапрошлого поста данного блога. На этот раз разговор пойдет о функциях. Обычные языки программирования (ЯП) имеют не только типизированные наборы данных - структурный тип, но и как ни странно, это распространяется и на функции (процедуры). То есть: каждая отдельная функция представляет собой совершенно отдельный тип. Происходит это от того, что сигнатуры функций могут различаться, как по количеству формальных аргументов, так и по их типу, и по их порядку размещения. А так как это все не поддается никакой систематизации с точки зрения языка, и отдано на откуп программистам, то и получается, что сигнатура функции представляет собой отдельный "функциональный" тип. Хорошо это или плохо я не знаю, вот и пытаюсь выяснить.. Плохо то, что порядок размещения, тип и количество аргументов накладывает дополнительную нагрузку на мозг программиста, так как все это надо помнить, а если и не помнить, то обращаться к докам, что замедляет процесс кодирования. К тому же, вызывающая функция обязана знать эту сигнатуру, иначе вызов вызываемой функции становится невозможен. А это означает одно: большую связность кода, что приводит к проблемам переносимости, расширения, изменения и т.д. Раньше, когда памяти было немного, такой подход был оправдан. Однако сейчас памяти у мас завались: её некуда девать, а производительность от этого не увеличилась, а продолжает падать.. Далее. По соглашениям функция (к примеру в языке Си или С++) оставляет возвращаемое значение в регистре EAX. Так уж повелось и закрепилось. Однако для передачи аргументов используется стек в памяти. Иначе невозможно обеспечить рекурсивность, когда функция, к примеру, вызывает сама себя. Однако в обычных приложениях рекурсивные алгоритмы используются крайне редко, поэтому парадигма, которая заточена только на то, что кто-то когда-то может использовать рекурсию, становится достаточно накладной. Допустим у функции есть два или три аргумента. Её вызывает другая функция. Эта другая знает, что вызываемая имеет три аргумента определенных типов. И вот, нам нужно изменить сигнатуру вызываемой: добавить четвертый аргумент. Добавили. При этом нам требуется изменить и код тех функций, которые вызывают данную, так как её сигнатура изменилась.. Это не просто плохо, а очень плохо, и считается дурным тоном. Но от этого никуда не деться. ООП прошу не предлагать. То есть изменился тип функции, теперь он стал другим, и по цепочке это повлияло на все вызывающие функции. Это плохо потому, что каждый программный компонент становится зависим от других, а ведь все мечтают о независимости модулей.. Оопщики это понимают, но нашли выход в другой, еще более развесистой лапше. Как можно было бы выйти из данной ситуации? Вот два примера:
Вот пример другого подхода:
Что мы видим в последнем примере? Ну, то, что теперь мы имеем функцию с одной точкой входа в неё. Именно с одной! То есть функция возвращает одно значение и принимает одно значение, хотя фактически работает с двумя! Через единственный указатель.. Появляется сразу вопрос: а что мы можем с этого получить? новую парадигму? Может быть, а может быть и нет. Но, то, что это приносит свои плюшки - очевидно. Если с одним структурным типом работает множество функций, то изменение этого типа, не приносит изменений этих других функций просто потому, что налицо наличие единственного типа сигнатуры функции, за исключением типа единственного аргумента. Но и это обстоятельство можно было бы преодолеть.. А как это уже другой вопрос.. |
Всего комментариев 97
Комментарии
-
Кстати, при повторном вызове concatS если первая строка не нуждается в изменении, её не нужно и модифицировать, передавая структуре только один аргумент:
А если не надо модифицировать и вторую строку, то аргументы трогать вообще не нужно:C 1 2 3 4 5 6 7 8
int main(void) { T d; d.c1=s1; d.c2=s2; printf("%s\n", concatS(&d)); d.c2=s2; printf("%s\n", concatS(&d));
При обычном использовании, пришлось бы заново передавать по два аргумента в функцию.C 1 2 3 4 5 6 7 8
int main(void) { T d; d.c1=s1; d.c2=s2; printf("%s\n", concatS(&d)); printf("%s\n", concatS(&d)); printf("%s\n", concatS(&d));
По сути, функция завязанная на структуру имеет свой особый интерфейс с внешним миром, который отделяет этот мир от деталей реализации. Если представить себе гипотетический язык, в котором эта концепция "от данных" могла бы полностью реализоваться, то мы получили бы более быстрый, ясный и поддерживаемый код. В данном случае (язык Си) это невозможно по органическим ограничениям, которые накладывает на разработчика данный язык.Запись от CoderHuligan размещена 17.02.2023 в 16:11 -
Иными словами , пользуясь жаргоном ООП, вы создали DataTransferObject . Если не нравится ООП, назовите Data Transfer Structure
Но, по сути вы ни чего не изменили. Что значит "вдруг" добавился еще аргумент? Т.е. у вас функция в реальном проекте может легко менять свой контракт? По хорошему, это уже не хорошо. Допустим это объективно необходимо. Добавляем аргумент и это нас заставит внимательно просмотреть все вызовы этой функции и принять решение по поводу нового параметра. Иначе большой риск что то забыть и получить баг, возможно хорошо замаскированный. Допустим в остальных случаях нам не важно значение этого параметра. Так может речь о значении по умолчанию? Ставим значение по умолчанию и вот ни чего не надо переписывать, там где это не важно.
Теперь рассмотрим ваш вариант со структурой которая может меняться. Это, на самом деле, скрывает проблему, а не решает.
1. Добавили новое свойство. Применили его при одном вызове, а в других местах, по своей сути используется "по умолчанию"
2. Убрали свойство.. И вот тут пошла "веселуха".
Т.е. DTO штука полезная для того чтобы не было 100500 параметров у функции. А для того, чтобы можно было "легко" менять ее контракт.... Штука опасная.
Для не больших проектов, которые пишешь в одиночку.... Может быть, но очень сомнительно. Для больших и где есть команда - опасная практика. (что правка DTO что правка набора параметров функции). При этом при смене сигнатуры ситуация более управляемая.Запись от voral размещена 17.02.2023 в 16:22 -
Если эта функция является интерфейсом библиотеки. Т.е. ее могут вызывать сторонние приложения и библиотеки - это вообще плохая истрия.
Запись от voral размещена 17.02.2023 в 16:23 -
Цитата:Убрали свойство.. И вот тут пошла "веселуха".
Цитата:Если эта функция является интерфейсом библиотеки. Т.е. ее могут вызывать сторонние приложения и библиотеки - это вообще плохая истрия.Запись от CoderHuligan размещена 17.02.2023 в 18:01 -
Цитата:
Цитата:
А так в общем то я и сказал, что ни чего нового вы не открыли. Этим пользуются.
Структуры удобны что б не городить бородилы параметров, группировать их по логике и т.п. А для озвученной вами проблемы они мало чего решают. По хорошему, что структуру изменили, что сигнатуру - проверять надо все. А так, чем отличается что вы в добавили свойство, что в параметры - у вас произошло изменение контракта данной функции. И надо обязательно принимать меры, проверять.
А по ссылке или по значению - тут, на мой взгляд, другие параметры для выбора должны быть. Т.е. если функция внезапно создаст сайдеффект и "отредактирует" входные данные. Это всегда ли ожидаемо... Типа отправляем printStruct(ourStruct) ожидая что она просто выведет в консоль, а там горе программист нафигачил костылей (ну или чего то атм "дебажил" и забыл)Запись от voral размещена 17.02.2023 в 18:58 -
При этом если мы добавляем параметр, то мы оцениваем важность:
- параметр обязательный - тут хоть со структурой хоть с параметрами мы должны будем править код, где вызывается функция
- параметр не обязательный - задаем значение по умолчанию.Запись от voral размещена 17.02.2023 в 19:05 -
Запись от XLAT размещена 17.02.2023 в 22:01
Обновил(-а) XLAT 17.02.2023 в 22:04 -
Запись от CoderHuligan размещена 18.02.2023 в 11:20 -
да, часто бывает, что правда никому не нравится.
этот чел был главный в мс - там в мс под его дудку плясали все индусы-погромисты.
разве винапи не похож на плоское гавнище - набор из неразбираемых функций без всякой иерархии.
да - не похож - а является этим самим гавнищем!
вы вот это не видите, а в том же ms это увидели,
пытались исправлять, например, gdi+.
ну, и основной апофеоз вдумчивого проектирования с раскаяниями за их говноапи у мс было с выходом FW.
щас уже почти середина 21 века.
выкиньте винапи на помойку и никогда его не трогайте.
я вам истину говорю.Запись от XLAT размещена 19.02.2023 в 09:05
Обновил(-а) XLAT 19.02.2023 в 09:14 -
Цитата:этот чел был главный в мс
Цитата:разве винапи не похож на плоское гавнище - набор из неразбираемых функций без всякой иерархии.
И наоборот: win api довольно понятны для настоящих профи. все остальные, кто не осилил говорят, что это неразбираемо. Ну, может где-то косяки и есть, но где их нет. Конечно апи не верх совершенства, признаю, но под win написано столько полезного софта на этих самых, причем хорошо работающего, что посылать на три буквы свои инструменты - как-то не комильфо. Если вы стойкий ненавистник виндовс и сидите на линукс тогда понятно. Но в линукс дыр гораздо больше, чем в виндовс: истину говорю!Запись от CoderHuligan размещена 19.02.2023 в 11:52 -
Запись от XLAT размещена 19.02.2023 в 15:39 -
Запись от CoderHuligan размещена 19.02.2023 в 15:44
Обновил(-а) CoderHuligan 19.02.2023 в 15:46 -
Вы напоминаете вот того самого школьника. Вспомните api, которые предоставляла ms dos. Вспомнили? В регистр AH заносился номер функции. В остальные регистры параметры, если таковые были. потом делался системный вызов/прерывание int21h. Ок? Без всякой системы, что касается параметров. А чем win api отличаются от dos api? Только объемом и большим удобством. Это ведь всего лишь прослойка между вызовами функций драйверов из кольца №0 и пользовательским уровнем. А как иначе? Объяснили бы. А уж поверх win api нагородили кучу библ, которые реально и пользуют все кому не лень. То есть вот на этом "гавнище", - удобрении, выросли прекрасные цветочки высокоуровневых либ, на вроде TK. И что - они плохо работают? Отнюдь нет. Тогда о чем вообще разговор?
Запись от CoderHuligan размещена 19.02.2023 в 15:55
Обновил(-а) CoderHuligan 19.02.2023 в 15:57 -
нет - не обязана,
держите:
https://rextester.com/IPJJM91230
не благодарите.C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
///----------------------------------------------------------------------------| /// ... ///---------------------------------------------------------------------------:1 #include <iostream> #include <string> namespace std { template<typename T> T to_wstring(T& s) { return s; } std::wstring to_wstring(char c) { return std::wstring(1, c); } std::wstring to_wstring(const char* c) { std::string s(c); return std::wstring(s.begin(), s.end()); } } template<typename ... T> auto concat(T ... t) { return (std::to_wstring(t) + ...); } int main() { setlocale(0, ""); std::wstring promt{L"Конкатенация в 21 веке: "}; std::wcout << concat(promt, "oderHuligan ", 2023, '\n'); std::wcout << concat(promt, 'C', L"oderХулиган ", 2023) << '\n' ; }
Запись от XLAT размещена 19.02.2023 в 16:28 -
Цитата:нет - не обязана,
Код:proc1 x=proc2(a, b, c) end proc1
Но proc1 конечно знать это не обязана. Для неё главное отдать свои переменные, а сколько их будет в сигнатуре функции ей знать не обязательно.. В идеале обращение должно идти через общий для всех интерфейс, например таблицу функций. Обращение идет к интерфейсу таблицы, а уж он делает вызов. Главное не прописывать жестко в самом коде вот эти сигнатуры, которые связывают код в один клубок. В идеале функции вообще знают только имена самих функций и общаются посредством внешних данных. Или через общую шину данных, например стек. Функция кладет на стек три параметра, но она не знает сколько уже положено туда до неё. Она не знает, что следующая берет со стека 4 параметра..
Поэтому изменение реализаций одних не сильно волнует остальных.Запись от CoderHuligan размещена 19.02.2023 в 17:44
Обновил(-а) CoderHuligan 19.02.2023 в 17:47 -
Запись от XLAT размещена 19.02.2023 в 20:03 -
Цитата:
При этом, как я выше сказал, но вы на это не отреагировали ситуацию (которую описали), на самом деле, "не исправили". Точно также если добавился новый параметр и он важный, мы должны поправить код везде, где это необходимо, если не важный - значит он имеет значение по умолчанию. Что так же решается "обычным" способом..
Между тем в реальности стоит задуматься, а правильно ли мы делаем, что меняем функцию, подозреваю, что в большинстве случаев, правильным решением будет сделать еще одну. Собственно так же задаться вопросом, а часто ли такое желание возникает в принципе?
И в итоге получаем что ради редких ситуаций вы предлагаете замусорить код заполнением структур.Запись от voral размещена 20.02.2023 в 00:09 -
При этом, повторюсь, струрктуры в качестве параметров могут быть полезны, но задача у них другая.
Запись от voral размещена 20.02.2023 в 00:10 -
Цитата:ВЫ ради "легкой" смены количества параметров функции, предлагаете каждую функцию, снабдить своей структурой. Т.е. теперь вместо "просто вызвать функцию", надо создавать структуру.
Цитата:Точно также если добавился новый параметр и он важный, мы должны поправить код везде, где это необходимо, если не важный - значит он имеет значение по умолчанию. Что так же решается "обычным" способом..Запись от CoderHuligan размещена 20.02.2023 в 11:25 -
А иначе ни как. Вы хотите добиться абсолютной несвязанности? так не бывает. Разве что искусственный интеллект подключать даже в функции вычисления квадрата. Точнее функция в каждом модуле остается одна, и уже сама както вычисляет "че хотел"
Так что все по прежнему.
Публичная функция модуля (экспортируемая наружу). Должна иметь четкий контракт. Это хорошо и правильно.
История о том, что она вдруг поменялась - попахивает не хорошим.
Т.е. по прежнему с одной стороны: если возникло желание добавить параметр - скорее всего это причина создать другую функцию.
С другой. ЕСли вам повезло взять в проект функцию, автор которой постоянно что то меняет: то тут самое правильное решение сделать обертку для нее. Т.е. в модуле создаем слой в котором будет "взаимодействие с внешним миром. Там создаем обертки, а вот уже в основном функционале - вызываем только обертку.
А вообще прежде чем начинать искать выход. Надо понять а есть ли проблема. Часто ли в реальных проектах встречаются ситуации когда есть объективные причины менять сигнатуру публичной функции?
Конечно могу привести тут же свой пример: в языке PHP есть ряд функций схожих по назначению, но у которых параметры (схожие по назначению) перепутаны местами. И это несколько "раздражает". Ну если вдруг решат поменять - ну это будет единичная ситуаций, которая крайне редка. в данном случае еще добавили поддержку именованных параметров (как в некоторых других ЯП, кстати) - так что может и не случиться никогда такого.
Цитата:Я об этом и говорю: если функции завязаны друг на друга, то изменение одной влечет изменения всех. а предположим, что код пишется командами, одна из которых ничего не знает о модулях другой, но вынуждена пользоваться общей библиотекой? Или интерфейсом модуля другой команды? Шило в одном месте.
Ради таких нерадивых разработчиков нет необходимости усложнять жизнь большинству.Запись от voral размещена 20.02.2023 в 12:31