Я много раз слышал, как другие разработчики используют эту фразу, чтобы «рекламировать» некоторые шаблоны или разрабатывать лучшие практики. Большую часть времени эта фраза используется, когда вы говорите о преимуществах функционального программирования.
Фраза «Легко рассуждать» использовалась как есть, без каких-либо объяснений или примеров кода. Так что для меня это похоже на очередное «модное» слово, которое более «опытные» разработчики используют в своих выступлениях.
Вопрос: Можете ли вы привести несколько примеров «Не легко рассуждать», чтобы их можно было сравнить с примерами «Легко рассуждать»?
Ответы:
На мой взгляд, фраза «легко рассуждать» относится к коду, который легко «выполнить в своей голове».
Если посмотреть на кусок кода, если он короткий, четко написанный, с хорошими именами и минимальной мутацией значений, то мысленно проработать, что делает код, - это (относительно) простая задача.
Длинный кусок кода с плохими именами, переменными, которые постоянно меняют значение, и извилистое ветвление обычно требуют, например, ручки и куска бумаги, чтобы помочь отследить текущее состояние. Поэтому такой код не может быть легко проработан просто в вашей голове, поэтому такой код нелегко рассуждать.
источник
"Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verification
- это звучит примерно как ответ на вопрос. Возможно, вы захотите опубликовать это как ответ вместо того, чтобы не соглашаться с тем, что (субъективный) ответ содержится в комментариях.Механизм или фрагмент кода легко рассуждать, когда вам нужно принять во внимание несколько вещей, чтобы предсказать, что он будет делать, и вещи, которые вы должны принять во внимание, легко доступны.
Истинные функции без побочных эффектов и состояния легко рассуждать, потому что выход полностью определяется входом, который находится прямо в параметрах.
И наоборот, объект с состоянием гораздо сложнее рассуждать, потому что вы должны учитывать, в каком состоянии находится объект, когда вызывается метод, что означает, что вы должны думать о том, какие другие ситуации могут привести к тому, что объект находится в конкретное состояние.
Еще хуже то, что глобальные переменные: рассуждая о коде, который читает глобальную переменную, вы должны понимать, где в вашем коде эта переменная может быть установлена и почему - и даже может быть нелегко найти все эти места.
Практически труднее всего рассуждать о многопоточном программировании с общим состоянием, поскольку у вас есть не только состояние, но и несколько потоков, изменяющих его одновременно, поэтому вы должны понять, что делает фрагмент кода при выполнении одним потоком. необходимо учитывать возможность того, что в каждой отдельной точке выполнения какой-либо другой поток (или несколько из них!) может выполнять практически любую другую часть кода и изменять данные, с которыми вы работаете, прямо на ваших глазах. Теоретически этим можно управлять с помощью мьютексов / мониторов / критических секций / как угодно, но на практике ни один простой человек на самом деле не может сделать это надежно, если только он не ограничивает общее состояние и / или параллелизм очень маленькими разделы кода.
источник
make
или даже специализация шаблона C ++ и перегрузка функций) могут вернуть вас в положение рассмотрения всей программы. Даже если вы думаете, что нашли определение чего-либо, язык позволяет более конкретному объявлению где-либо в программе переопределять его. Ваша IDE может помочь с этим.sealed
не быть по умолчанию?В случае функционального программирования значение «Легко рассуждать» в основном состоит в том, что оно является детерминированным. Под этим я подразумевал, что данный ввод всегда будет приводить к одному и тому же выводу. Вы можете делать с программой все, что хотите, до тех пор, пока вы не коснетесь этого куска кода, он не сломается.
С другой стороны, OO, как правило, сложнее рассуждать, потому что получаемый «результат» зависит от внутреннего состояния каждого задействованного объекта. Типичный способ, которым это проявляется, - неожиданные побочные эффекты : при изменении одной части кода внешне несвязанная часть разрывается.
... Недостатком функционального программирования, конечно, является то, что на практике многое из того, что вы хотите сделать, - это ввод-вывод и управление состоянием.
Тем не менее, есть много других вещей, о которых сложнее рассуждать, и я согласен с @Kilian, что параллелизм является ярким примером. Распределенные системы тоже.
источник
Избежание более широкого обсуждения и решение конкретного вопроса:
Я отсылаю вас к «Истории Мела, настоящего программиста» , куска фольклора программистов, который датируется 1983 годом и поэтому считается нашей легендой для нашей профессии.
Он рассказывает историю программиста, пишущего код, который предпочитал тайные методы, где это возможно, включая самоссылочный и самоизменяющийся код, и преднамеренную эксплуатацию ошибок машины:
Это пример кода, который «трудно рассуждать».
Конечно, Мел не согласится ...
источник
Я могу привести пример, и очень распространенный.
Рассмотрим следующий код C #.
Теперь рассмотрим эту альтернативу.
Во втором примере я точно знаю, что этот код делает с первого взгляда. Когда я вижу
Select
, я знаю, что список предметов превращается в список чего-то еще. Когда я вижуWhere
, я знаю, что некоторые элементы отфильтровываются. Сnames
первого взгляда я могу понять, что это такое, и эффективно использовать его.Когда я вижу
for
цикл, я понятия не имею, что с ним происходит, пока не прочитаю код. И иногда мне приходится прослеживать это, чтобы убедиться, что я учел все побочные эффекты. Мне нужно немного поработать, чтобы даже понять, что такое имена (помимо определения типа) и как эффективно их использовать. Таким образом, первый пример сложнее рассуждать, чем второй.В конечном счете, легкость рассуждений здесь также зависит от понимания методов
Select
и LINQWhere
. Если вы их не знаете, то второй код будет сложнее рассуждать изначально. Но вы платите только за то, чтобы понять их один раз. Вы платите за пониманиеfor
цикла каждый раз, когда используете его, и каждый раз, когда он меняется. Иногда стоимость того стоит, но обычно «легче рассуждать» гораздо важнее.источник
Связанная фраза (я перефразирую),
Примером относительно "легкого рассуждения" может быть RAII .
Другим примером может быть предотвращение смертельного объятия : если вы можете удерживать блокировку и получить другую блокировку, и существует множество блокировок, трудно быть уверенным, что не существует сценария, в котором может произойти смертельное объятие. Добавление правила, такого как «есть только одна (глобальная) блокировка» или «вам не разрешено получать вторую блокировку, пока вы удерживаете первую блокировку», позволяет относительно легко рассуждать о системе.
источник
CComPtr<>
) с функцией в стиле C (CoUninitialize()
). Я также нахожу это странным примером, насколько я помню, вы вызываете CoInitialize / CoUninitialize в области видимости модуля и для всего времени жизни модуля, например, внутриmain
или внутриDllMain
, а не в какой-то крошечной недолговечной локальной области действия функции, как показано в примере ,main
функцией точки входа ( ) для приложения. Вы инициализируете COM при запуске, а затем неинициализируете его прямо перед выходом. За исключением того, что у вас есть глобальные объекты, такие как смарт-указатели COM, использующие парадигму RAII. Что касается стилей микширования: глобальный объект, который инициализирует COM в своем ctor и неинициализирован в своем dtor, является работоспособным, и то, что предлагает Рэймонд, но это тонкий и непростой аргумент.Суть программирования - анализ случаев. Алан Перлис отметил это в эпиграмме № 32: программисты должны измеряться не их изобретательностью и логикой, а полнотой анализа их случаев.
Ситуацию легко рассуждать, если анализ случая прост. Это либо означает, что есть несколько случаев для рассмотрения, либо, если это не так, мало особых случаев - это могут быть большие пространства случаев, которые разрушаются из-за некоторых закономерностей или поддаются методике рассуждения, такой как индукция.
Например, рекурсивную версию алгоритма обычно легче рассуждать, чем императивную версию, потому что она не содержит лишних случаев, возникающих из-за мутации вспомогательных переменных состояния, которые не появляются в рекурсивной версии. Более того, структура рекурсии такова, что она вписывается в математическую схему доказательства по индукции. Нам не нужно учитывать сложности, такие как варианты цикла и самые слабые строгие предварительные условия и все такое.
Другим аспектом этого является структура корпуса. Проще рассуждать о ситуации, в которой есть плоское или в основном плоское деление на случаи, по сравнению с иерархической ситуацией: случаи с подслоями и подслоями и т. Д.
Свойство систем, которое упрощает рассуждение, является ортогональностью : это свойство, когда случаи, которые управляют подсистемами, остаются независимыми, когда эти подсистемы объединяются. Никакие комбинации не приводят к «особым случаям». Если что-то с четырьмя случаями объединено с чем-то с тремя случаями ортогонально, есть двенадцать случаев, но в идеалекаждый случай представляет собой комбинацию двух случаев, которые остаются независимыми. В некотором смысле, на самом деле нет двенадцати случаев; комбинации - это просто "возникающие явления типа случая", о которых нам не нужно беспокоиться. Это означает, что у нас все еще есть четыре случая, о которых мы можем думать, не рассматривая остальные три в другой подсистеме, и наоборот. Если некоторые из комбинаций должны быть специально определены и наделены дополнительной логикой, то рассуждение является более сложным. В худшем случае каждая комбинация имеет особую обработку, и тогда действительно есть двенадцать новых случаев, которые являются дополнением к оригинальным четырем и трем.
источник
Конечно. Возьми параллелизм:
Критические секции, навязанные мьютексами: легко понять, потому что существует только один принцип (два потока исполнения не могут одновременно войти в критическую секцию), но подвержены как неэффективности, так и тупику.
Альтернативные модели, например, программирование без блокировок или актеры: потенциально гораздо более элегантные и мощные, но адски трудно понять, потому что вы больше не можете полагаться (на первый взгляд) на фундаментальные концепции, такие как «теперь записать это значение в это место».
Быть легко рассуждать - это один из аспектов метода. Но выбор того, какой метод использовать, требует рассмотрения всех аспектов в сочетании.
источник
Давайте ограничим задачу формальными рассуждениями. Потому что юмористические, изобретательные или поэтические рассуждения имеют разные законы.
Несмотря на это, выражение определено слабо и не может быть задано строго. Но это не значит, что он должен оставаться таким тусклым для нас. Давайте представим, что структура проходит некоторый тест и получает оценки за разные точки. Хорошие оценки для КАЖДОГО пункта означают, что структура удобна в каждом аспекте и, таким образом, «легко рассуждать».
Структура «Легко рассуждать» должна получить хорошие оценки за следующее:
Является ли тест субъективным? Да, естественно, это так. Но само выражение тоже субъективно. То, что легко для одного человека, не легко для другого. Итак, тесты должны быть разными для разных доменов.
источник
Идея о том, что функциональные языки можно рассуждать, вытекает из их истории, в частности ML, который был разработан как язык программирования, аналогичный конструкциям, которые логика для вычислимых функций использовала для рассуждения. Большинство функциональных языков ближе к формальным вычислениям программирования, чем к императивным, поэтому перевод из кода на ввод системы рассуждений менее обременителен.
Для примера системы рассуждений в pi-исчислении каждое изменяемое место в памяти в императивном языке должно быть представлено как отдельный параллельный процесс, тогда как последовательность функциональных операций - это отдельный процесс. Сорок лет спустя из программы проверки теорем LFC мы работаем с ГБ ОЗУ, поэтому наличие сотен процессов не представляет особой проблемы - я использовал pi-исчисление для удаления потенциальных тупиков из нескольких сотен строк C ++, несмотря на то, что представление имеет сотни Процессы, которые рассуждал, исчерпали пространство состояний примерно в 3 ГБ и вылечили прерывистую ошибку. Это было бы невозможно в 70-х годах или потребовался бы суперкомпьютер в начале 90-х годов, тогда как пространство состояний программы функционального языка аналогичного размера было достаточно маленьким, чтобы рассуждать об этом тогда.
Из других ответов эта фраза превращается в модную фразу, хотя большая часть трудностей, из-за которых было трудно рассуждать о императивных языках, размыта законом Мура.
источник
Легко рассуждать - это культурно-специфический термин, поэтому так сложно привести конкретные примеры. Это термин, который привязан к людям, которые должны рассуждать.
«Легко рассуждать» на самом деле очень самоописательная фраза. Если кто-то смотрит на код и хочет рассуждать о том, что он делает, это легко =)
Ладно, сломай это. Если вы смотрите на код, вы обычно хотите, чтобы он что-то сделал. Вы хотите убедиться, что он делает то, что, как вы думаете, он должен делать. Таким образом, вы разрабатываете теории о том, что должен делать код, а затем рассуждаете об этом, пытаясь спорить, почему код действительно работает. Вы пытаетесь думать о коде как о человеке (а не как о компьютере) и пытаетесь рационализировать аргументы о том, что может делать код.
Наихудший случай для «легкого рассуждения» - это когда единственный способ понять, что делает код, - это построчно пройти код, как машина Тьюринга, для всех входных данных. В этом случае единственный способ что-либо объяснить в коде - это превратить себя в компьютер и выполнить его в своей голове. Эти наихудшие примеры легко увидеть в запутанных соревнованиях по программированию, таких как эти 3 строки PERL, которые расшифровывают RSA:
Что касается простоты рассуждений, опять же, термин очень культурный. Вы должны рассмотреть:
Каждый из них влияет на «легкость рассуждения» по-своему. Возьмите навыки мыслителя в качестве примера. Когда я начинал в своей компании, мне было рекомендовано разрабатывать свои скрипты в MATLAB, потому что это «легко рассуждать». Почему? Ну, все в компании знали MATLAB. Если бы я выбрал другой язык, мне было бы труднее понять меня. Не имеет значения, что читаемость MATLAB ужасна для некоторых задач, просто потому, что она не предназначена для них. Позже, по мере развития моей карьеры, Python становился все более и более популярным. Внезапно код MATLAB стал «трудно рассуждать», и Python стал языком предпочтений для написания кода, о котором было легко рассуждать.
Также подумайте, что может быть у читателя. Если вы можете рассчитывать на то, что ваш читатель распознает БПФ в определенном синтаксисе, «проще рассуждать» о коде, если вы будете придерживаться этого синтаксиса. Это позволяет им смотреть на текстовый файл как на холст, на котором вы нарисовали БПФ, вместо того, чтобы вдаваться в мельчайшие детали. Если вы используете C ++, выясните, насколько ваши читатели знакомы с
std
библиотекой. Насколько им нравится функциональное программирование? Некоторые идиомы из библиотек контейнеров очень зависят от того, какой стиль вы предпочитаете.Также важно понимать, на какие вопросы читателю может быть интересно ответить. Ваши читатели в основном озабочены поверхностным пониманием кода или ищут ошибки глубоко в недрах?
Насколько уверен, что читатель должен быть на самом деле интересный. Во многих случаях смутных рассуждений на самом деле достаточно, чтобы вывести продукт за дверь. В других случаях, таких как программное обеспечение полета FAA, читатель захочет иметь железные аргументы. Я столкнулся с делом, в котором приводил доводы в пользу использования RAII для конкретной задачи, потому что «вы можете просто настроить его и забыть об этом ... это будет правильно». Мне сказали, что я был неправ в этом. Те, кто собирался рассуждать по этому коду, не были людьми, которые «просто хотят забыть о деталях». Для них RAII был больше похож на чада, заставляющего их думать обо всем, что может случиться, когда вы покидаете сферу.
источник