Вот проблема программирования / языка, о которой я хотел бы услышать ваши мысли.
Мы разработали соглашения, которым должно следовать большинство программистов, которые не являются частью синтаксиса языков, но служат для того, чтобы сделать код более читабельным. Это, конечно, всегда предмет спора, но есть, по крайней мере, некоторые основные концепции, которые большинство программистов считают приемлемыми. Называя ваши переменные соответствующим образом, называя в целом, делая ваши строки не слишком длинными, избегая длинных функций, инкапсуляций и тому подобного.
Однако есть проблема, которую я пока не нашел, чтобы кто-то комментировал, и это может быть самой большой проблемой. Это проблема анонимности аргументов при вызове функции.
Функции вытекают из математики, где f (x) имеет ясное значение, потому что функция имеет гораздо более строгое определение, чем обычно в программировании. Чистые функции в математике могут делать намного меньше, чем в программировании, и они являются гораздо более элегантным инструментом, они обычно принимают только один аргумент (обычно это число) и всегда возвращают одно значение (также обычно число). Если функция принимает несколько аргументов, они почти всегда являются просто дополнительными измерениями области функции. Другими словами, один аргумент не важнее других. Конечно, они явно упорядочены, но кроме этого у них нет семантического упорядочения.
Однако в программировании у нас больше свободы в определении функций, и в этом случае я бы сказал, что это не очень хорошая вещь. Распространенная ситуация, у вас есть функция, определенная следующим образом
func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}
Глядя на определение, если функция написана правильно, совершенно ясно, что к чему. При вызове функции в вашей IDE / редакторе может даже возникнуть какая-то магия intellisense / завершения кода, которая скажет вам, каким должен быть следующий аргумент. Но ждать. Если мне это нужно, когда я на самом деле пишу звонок, неужели здесь что-то не хватает? Человек, читающий код, не имеет преимущества IDE, и, если он не перейдет к определению, он не знает, какой из двух прямоугольников, переданных в качестве аргументов, для чего используется.
Проблема идет даже дальше, чем это. Если наши аргументы получены из какой-то локальной переменной, могут быть ситуации, когда мы даже не знаем, что такое второй аргумент, поскольку видим только имя переменной. Возьмите для примера эту строку кода
DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])
Это распространяется на различные степени в разных языках, но даже в строго типизированных языках, и даже если вы разумно называете свои переменные, вы даже не упоминаете тип переменной, когда передаете ее функции.
Как обычно бывает с программированием, есть много потенциальных решений этой проблемы. Многие уже реализованы на популярных языках. Именованные параметры в C # например. Однако все, что я знаю, имеет существенные недостатки. Наименование каждого параметра при каждом вызове функции не может привести к читабельности кода. Такое ощущение, что мы, возможно, перерастаем возможности, которые дает нам простое текстовое программирование. Мы перешли от текста просто в почти каждой области, но мы все еще кодируем то же самое. Нужно больше информации для отображения в коде? Добавьте больше текста. В любом случае, это становится немного тангенциальным, поэтому я остановлюсь здесь.
Один ответ, который я получил на второй фрагмент кода, заключается в том, что вы, вероятно, сначала распакуете массив для некоторых именованных переменных, а затем будете использовать их, но имя переменной может означать много вещей, и то, как она вызывается, не обязательно говорит о том, как она должна интерпретироваться в контексте вызываемой функции. В локальной области у вас может быть два прямоугольника с именами leftRectangle и rightRectangle, потому что это то, что они семантически представляют, но не нужно расширять то, что они представляют при передаче функции.
Фактически, если ваши переменные названы в контексте вызываемой функции, вы вводите меньше информации, чем могли бы с этим вызовом функции, и на некотором уровне, если это приводит к ухудшению кода. Если у вас есть процедура, результатом которой является прямоугольник, который вы сохраняете в rectForClipping, а затем другая процедура, которая обеспечивает rectForDrawing, то фактический вызов DrawRectangleClipped - просто церемония. Линия, которая не означает ничего нового и существует только для того, чтобы компьютер знал, чего именно вы хотите, даже если вы уже объяснили это с помощью имен. Это не очень хорошая вещь.
Я бы очень хотел услышать свежие взгляды на это. Я уверен, что я не первый, кто считает эту проблему проблемой, так как она решается?
Ответы:
Я согласен, что то, как часто используются функции, может быть запутанной частью написания кода, и особенно чтения кода.
Ответ на эту проблему частично зависит от языка. Как вы упомянули, C # имеет именованные параметры. Решение Objective-C этой проблемы включает в себя более описательные имена методов. Например,
stringByReplacingOccurrencesOfString:withString:
это метод с четкими параметрами.В Groovy некоторые функции принимают карты, допускающие синтаксис, подобный следующему:
В общем, вы можете решить эту проблему, ограничив число параметров, передаваемых функции. Я думаю, что 2-3 хороший предел. Если кажется, что функции нужно больше параметров, это заставляет меня переосмыслить дизайн. Но это может быть сложнее ответить в целом. Иногда вы пытаетесь сделать слишком много в функции. Иногда имеет смысл рассмотреть класс для хранения ваших параметров. Кроме того, на практике я часто нахожу, что функции, которые принимают большое количество параметров, обычно имеют многие из них как необязательные.
Даже в таком языке, как Objective-C, имеет смысл ограничить количество параметров. Одна из причин заключается в том, что многие параметры являются необязательными. Для примера, см. RangeOfString: и его варианты в NSString .
Шаблон, который я часто использую в Java, - это использование класса в свободном стиле в качестве параметра. Например:
Это использует класс в качестве параметра, и с классом свободного стиля, делает для легко читаемого кода.
Приведенный выше фрагмент кода Java также помогает в тех случаях, когда упорядочение параметров может быть не столь очевидным. Мы обычно предполагаем с координатами, что X предшествует Y. И я обычно вижу высоту перед шириной как соглашение, но это все еще не очень ясно (
something.draw(5, 20)
).Я также видел некоторые функции, например,
drawWithHeightAndWidth(5, 20)
но даже они не могут принимать слишком много параметров, иначе вы начнете терять читабельность.источник
Dimension(int width, int height)
иGridLayout(int rows, int cols)
(количество строк - это высота, значениеGridLayout
имеет высоту в первую очередь иDimension
ширину).array_filter($input, $callback)
противarray_map($callback, $input)
иstrpos($haystack, $needle)
противarray_search($needle, $haystack)
В основном это решается с помощью хорошего именования функций, параметров и аргументов. Вы уже исследовали это и обнаружили, что в нем есть недостатки. Большинство из этих недостатков устраняются за счет небольшого количества функций с небольшим количеством параметров как в вызывающем контексте, так и в вызываемом контексте. Ваш конкретный пример проблематичен, потому что функция, которую вы вызываете, пытается сделать несколько вещей одновременно: указать базовый прямоугольник, указать область отсечения, нарисовать ее и залить ее определенным цветом.
Это похоже на попытку написать предложение, используя только прилагательные. Поместите туда больше глаголов (вызовов функций), создайте тему (объект) для вашего предложения, и это будет легче читать:
Даже если
clipRect
иcolor
имеют ужасные имена (а они не должны), вы все равно можете различить их типы из контекста.Ваш десериализованный пример проблематичен, потому что вызывающий контекст пытается сделать слишком много одновременно: десериализацию и отрисовку чего-либо. Вы должны назначить имена, которые имеют смысл, и четко разделить две обязанности. Как минимум, примерно так:
Многие проблемы с читабельностью вызваны попыткой быть слишком лаконичным, пропуская промежуточные этапы, которые требуются людям для распознавания контекста и семантики.
источник
На практике это решается лучшим дизайном. Чрезвычайно необычно, когда хорошо написанные функции принимают более 2 входных данных, и когда это происходит, эти входные данные нередко не могут быть объединены в какой-то связный пакет. Это позволяет довольно легко разбивать функции или агрегировать параметры, поэтому вы не заставляете функцию делать слишком много. Один из них имеет два входа, становится легко назвать и гораздо понятнее, какой вход какой.
У моего игрушечного языка была концепция фраз, чтобы справиться с этим, и у других более естественных языков программирования, ориентированных на язык, были другие подходы, чтобы справиться с этим, но у всех них есть другие недостатки. Кроме того, даже фразы - это всего лишь приятный синтаксис, заключающийся в том, что у функций есть лучшие имена. Всегда будет сложно сделать хорошее имя функции, когда требуется несколько входов.
источник
В Javascript (или ECMAScript ), к примеру, многие программисты привыкли к
И как практика программирования, она попала от программистов к их библиотекам и оттуда к другим программистам, которые полюбили это, используют его и пишут еще несколько библиотек и т. Д.
пример
Вместо звонка
как это:
, который является действительным и правильным стилем, вы называете
как это:
, который является действительным, правильным и приятным в отношении вашего вопроса.
Конечно, для этого должны быть подходящие условия - в Javascript это гораздо более жизнеспособно, чем, скажем, в C. В javascript это даже породило широко используемую в настоящее время структурную нотацию, которая стала популярной в качестве более легкого аналога XML. Он называется JSON (возможно, вы уже слышали об этом).
источник
params
(часто содержащие необязательные аргументы и часто сами необязательные), например, эта функция . Это делает функции со многими аргументами довольно легко понять (в моем примере есть 2 обязательных аргумента и 6 параметров аргументов).Затем вы должны использовать target-C, вот определение функции:
И здесь это используется:
Я думаю, что ruby имеет аналогичные конструкции, и вы можете смоделировать их на других языках с помощью списков ключей-значений.
Для сложных функций в Java мне нравится определять фиктивные переменные в формулировке функций. Для вашего примера слева направо:
Похоже, больше кодирования, но вы можете, например, использовать leftRectangle, а затем рефакторинг кода позже с «Извлечь локальную переменную», если вы думаете, что он не будет понят для будущего сопровождающего кода, который может быть или не быть вами.
источник
Мой подход заключается в создании временных локальных переменных, а не просто вызывать их
LeftRectange
иRightRectangle
. Скорее я использую несколько более длинные имена, чтобы передать больше значения. Я часто стараюсь максимально различать имена, например, не называть их обоихsomething_rectangle
, если их роль не очень симметрична.Пример (C ++):
и я мог бы даже написать однострочную функцию-оболочку или шаблон:
(игнорируйте амперсанды, если вы не знаете C ++).
Если функция делает несколько странных вещей без хорошего описания - тогда, вероятно, ее необходимо реорганизовать!
источник