Статьи

Почему фундаментальная теорема разработки программного обеспечения является фундаментальной?

 Слышали ли вы о пословице « Все проблемы в информатике могут быть решены с помощью другого уровня косвенности». ? Если вы программист, скорее всего, вы читали об этом в книге или в статье, рассказывающей о том, как лучше структурировать написанное вами программное обеспечение. Это было названо  фундаментальной теоремой разработки программного обеспечения (FTSE),  поэтому вы должны знать, о чем она. Если вы этого не сделаете, быстро прочитайте эту тему на … нет, я не буду делать поиск в Google.

Общее объяснение, которое вы найдете, заключается в том, что FTSE говорит об уровнях абстракции: еще один уровень косвенности достигается путем повышения уровня абстракции. Это хороший способ просмотра, но только некоторые из них. Абстракция, когда реализуется в программном обеспечении, часто приводит к созданию слоя, так что детали того, что находится на другой стороне, остаются скрытыми, и слой заставляет ссылки идти по кругу о способах достижения точки, отсюда и косвенность. Однако существуют и другие формы косвенного обращения, когда нужно просто уменьшить связь между двумя частями. Здесь мы на самом деле не избавляемся от каких-либо деталей, но, возможно, создаем общий язык о них. Подумайте о шаблоне адаптера, например. Не убежден? Прочитайте проницательный комментарий на эту тему Зеда Шоу по адресу  http://zedshaw.com/essays/indirection_is_not_abstraction.html.

Итак, просто для удовольствия, как бы вы объяснили FTSE для неофита? Википедия определяет  косвенность  как  способность ссылаться на что-либо, используя имя, ссылку или контейнер вместо самого значения . Это вводит в заблуждение и разоблачает. Вводит в заблуждение, потому что это гораздо более общее определение, чем большинство программистов по поводу FTSE. В конце концов, все зависит от имен и ссылок, так чему же может научить нас теорема, утверждающая очевидное? Но это также откровение, потому что оно намекает на тот факт, что большая часть разработки программного обеспечения и, следовательно, большая часть вычислений, заключается в ответе на столь же известный вопрос,  что в названии, Поэтому, чтобы применить FTSE на практике, мы должны позволить себе ответить на этот вопрос как можно лучше в любой момент времени. То есть нам нужно уметь определять значение имени в любом данном контексте. Значение символа в программной системе даже средней сложности быстро начинает проявлять нюансы и претерпевает изменения, очень похожие на слова в естественном языке. Это связано с тем, что подобно тому, как естественный язык является сложной символической моделью грязного мира, программная система может быть охарактеризована аналогичным образом.

В каком-то смысле программное обеспечение, любое программное обеспечение — это модель мира. Символы, которые мы используем в программном обеспечении, приобретают значение (или семантику, если хотите) благодаря тому, что сущности, на которые они ссылаются, фактически делают. Смысл функции заключается в ее реализации. Смысл части данных в том, как она используется, как она взаимодействует с другими данными. И типы программирования в некоторой степени регулируют это использование, но в основном это то, что мы делаем с данными внутри программы. Например, чтобы узнать значение поля в структуре данных, о котором у вас нет документации, вы должны отследить, где и как оно используется. Новые требования или изменения и корректировки старых требований — не что иное, как усовершенствованная модель мира и / или системы, которая должна быть отражена в программном обеспечении.Этот процесс уточнения модели обогащает значение терминов, используемых для описания вещей, и это приводит к изменению семантики определенных символов в программном обеспечении. Что мы называем «изменением реализации». Теперь практика программирования не рассматривается как создание и уточнение значения символовНо я считаю, что это очень важная перспектива. Это перспектива, в которой разрешение символических ссылок в контексте лежит в основе программирования, а не обычного представления «давать инструкции машине». Я пришел к такому выводу около 15 лет назад, когда проектировал язык программирования и изучал различные теоретические конструкции в дизайне PL, их ограничения и компромиссы. За прошедшие годы я разработал рефлекс, чтобы увидеть множество проблем проектирования с точки зрения важных символов в игре, контекста, лексического и времени выполнения (или статического и динамического, если вы предпочитаете), и каков процесс разрешения этих символических символов. Ссылки. Я также вижу множество конструкций языка программирования как набор инструментов для разрешения ссылок. Это,Программные конструкции в значительной степени являются инструментами, которые программист имеет в своем распоряжении для определения значения символических ссылок. Давайте посмотрим на некоторые.

переменные


Переменные являются наиболее важным инструментом программирования. Вероятно, это первая конструкция, которую вы когда-либо изучали в программировании, непосредственно взятая из алгебры, это самая простая форма абстракции — используйте имя вместо значения. Конечно, как предполагает термин «переменная», весь смысл в том, чтобы сама вещь изменилась, что-то, что может меняться. Таким образом, в действительности переменная устанавливает идентичность и интерфейс для изменений, которые должны быть затронуты, таким образом, обходя все виды метафизических проблем в одном хорошем техническом анализе. Концептуально переменные — это прямые символические ссылки, ассоциации между именем и значением «контейнер». С точки зрения реализации, они, как правило, аналогичны указателям на ячейки памяти, и их всегда понимали. По факту,это понимание является следствием того факта, что в скомпилированных языках имя переменной исчезает из скомпилированной версии, оно заменяется местом в памяти. Преимущество этой стратегии состоит в том, что стоимость использования переменной настолько низка, насколько это возможно — просто доступ к ячейке памяти RAM. С другой стороны, любая гибкость в том, как имя должно интерпретироваться во время выполнения, полностью исчезла.

Обратите внимание, что мы не говорим здесь о различии между «ссылочными переменными» и «примитивными переменными данных» или чем-то еще. Хотя это важное различие, нас беспокоит лишь тот факт, что переменные являются названиями вещей. То, что на разных языках считается «ссылочными переменными» (или указателями), связано с тем, как они копируются во время операции присваивания или как аргумент функции, независимо от того, является ли значение изменяемым или нет и т. Д.

Псевдонимы и макросы

Псевдонимы относительно редки в качестве отдельной конструкции в современных языках. Когда указатели назначаются друг другу, это называется
псевдонимами,  потому что у нас есть два имени для одной и той же ячейки памяти, т.е. две ссылки с одним и тем же референтом. Например, присвоение любой переменной 
объекта тип в Java считается псевдонимом. Хотя у нас здесь есть другой уровень косвенности, поскольку мы можем изменить одну ссылку, не нарушая другую, этот тип псевдонимов не особенно интересен. Но рассмотрим другой тип псевдонимов через макросы (например, C #define), где имя явно объявляется заменой другого имени. Переадресация здесь включает в себя дополнительный шаг перевода, и значение псевдонима в данном случае не в том, что у него тот же референт, а в том, что его референтом является исходное имя. Как следствие, упоминание псевдонима в месте программы, где один и тот же символ используется для совершенно другой вещи, заставит псевдоним ссылаться на эту вещь. Другое преимущество этого вида псевдонимов заключается в том, что его можно использовать для ссылки на другие вещи, кроме переменных, например, типов,особенно когда описание типа является сложным выражением, которое мы не хотим писать снова и снова. Макросы также находятся в том же духе — языки, которые признают ценность принятия решений во время компиляции, предложат возможность макросов. И обидно, они не более популярны. У них просто плохая аура, потому что они связаны с чисто текстовой манипуляцией, совершенно отдельным языком. Однако, если рассматривать их как способ создания структурированного кода или оценки во время компиляции, они гораздо более привлекательны. Нужно понимать, что макросы и псевдонимы так же важны как имя и победитель, как и переменные и функции, и что на самом деле они являются механизмом косвенного обращения. Механизм, который занимает другое место в измерении времени компиляции. Говоря о которых,тот факт, что между этим строгим разделением между компиляцией и временем выполнения нет ничего, является сильным ограничением языковой выразительности. Методы частичной оценки могут быть такими, какими могут быть макросы во время выполнения, но в основном они все еще ограничиваются научными исследованиями.

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

Продолжить чтение…

перегрузка


Перегрузка — это программный механизм, который позволяет определять разные реализации одной и той же функции в зависимости от аргументов, используемых во время вызова. Другими словами, перегрузка позволяет ассоциировать различное значение с данным именем в зависимости от синтаксического контекста использования этого имени. Это контекстно-зависимый процесс разрешения ссылок, который обычно происходит во время компиляции, следовательно, в статическом контексте. Грубым аналогом естественного языка будут омонимы, которые имеют разные значения только потому, что используются в качестве другой части речи. Например, в словах «все реки текут» и «поток гладкий» смысловой смысл один и тот же, но строгий смысл другой, потому что в одном случае у нас есть глагол, а в другом — субъект. Вариация на тему
Common Lisp и его универсальные функции, в которых диспетчеризация может быть определена для реального объекта с помощью предиката equals. В этом случае контекст для разрешения является динамическим, поэтому в некотором смысле он больше связан с семантикой. Это больше похоже на омонимию, где семантика слова зависит от семантики окружающих слов.

Переопределение


Переопределение — это изменение значения имени во вложенной лексической области. Я намеренно использую здесь слово « 
значение»,  чтобы говорить о том, на что ссылается имя, понимая, что часто значение символа в языке программирования является просто референтом этого символа. Вложенная лексическая область может быть вложенной функцией, или объявлением класса, или каким-либо кодом, который ограничивает лексическую область. В некоторых случаях теряется возможность доступа к референту из окружающей области. В других, язык программирования обеспечивает специальный синтаксис для этого. (т.е. 
супер ключевое слово в Java). Опять же, речь идет о механизме разрешения ссылок в определенном специализированном контексте. Контекст полностью определяется лексической областью и поэтому является статическим. Обратите внимание, что люди несколько ошибочно используют слово 
перезапись  вместо
переопределения . Правильный термин в информатике
переопределен . В английском это означает отменить предыдущее решение, тогда как
переписать буквально означает переписать что-то. Подробнее о механике переопределения ниже.

Классы 


Составная структура, такая как объект в JavaScript, или класс в Java / C #, или структура в C — это, помимо прочего, контекст, в котором определенные имена имеют значения. В частности, поля и методы, которые принадлежат этой структуре, являются ссылками, которые разрешаются именно в контексте объекта времени выполнения. Ну, на самом деле это зависит. Интересным случаем являются статические переменные в классах Java. Статическая переменная является членом объекта класса, а не его экземпляров. Один из способов, которым учителя языка описывают статические переменные, состоит в том, что все объекты этого класса имеют одну и ту же переменную. Это даже то, как в официальной документации Java говорится о статических переменных: с точки зрения переменных, которые разделяют все объекты (см. 
Http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html.). Но это неточно, потому что объект (то есть экземпляр класса) не является надлежащим контекстом для ссылки на статическую переменную. Если у нас есть:

class A { public static int a = 0; }
class B extends A { public static int a  = 1;}
A x = new B();
System.out.println(x.a); // what are we supposed to see here?

Что распечатывает последнее утверждение? Это звучит как хороший вопрос собеседования для начинающих. Вопрос сводится к тому, каков надлежащий контекст для эталонного разрешения имени
a ? Если мы видим статические переменные как переменные, совместно используемые всеми объектами одного и того же класса, значение, очевидно, должно быть равно 1, поскольку фактический тип x равен
B, а
Ba — то, что разделяют все объекты типа
B. Но это не то, что происходит. Мы получаем 0, потому что Java заботится только об объявленном типе, который в этом является
A, Правильный способ говорить о статических переменных — использовать переменные-члены класса объекта (в Java каждый класс во время выполнения является объектом, типом которого является java.lang.Class). Вот почему некоторые недавние Java-компиляторы выдают предупреждение, когда вы ссылаетесь на статическую переменную из контекста объекта (но не компилятора JDK7!). Чтобы быть справедливым по отношению к официальной документации, вышеупомянутое руководство действительно рекомендует использовать класс, а не объект ссылаться на статические переменные. Тем не менее, причина в том, что в противном случае 
это не дает понять, что они являются переменными класса . Теперь, если бы у нас были нестатические переменные-члены? Мы получаем тот же результат: объявленный тип переменной объекта
xимеет значение, а не фактический тип времени выполнения. Если бы вместо доступа к публичной переменной мы обращались к публичной функции, тогда для разрешения ссылки использовался бы тип времени выполнения.

Так почему же это так? Потому что часть процесса разрешения ссылок происходит во время компиляции, а не во время выполнения. Традиционно, в языках программирования имя переводится в ячейку памяти во время компиляции, поэтому во время выполнения имеет значение только местоположение, и референт получается самым быстрым способом. С помощью так называемых «виртуальных методов», таких как нестатические методы в Java, мы делаем дополнительный прыжок, чтобы попасть в область памяти во время выполнения. Таким образом, для переменных, как статических, так и нестатических, а также для статических методов, эталонное разрешение полностью основано на статическом контексте (аннотации типов, доступные во время компиляции), в то время как для нестатической функции оно становится динамическим. Почему только один вид имени в лексическом контексте класса разрешается таким образом. Нет никакой глубокой причины, просто как Java была разработана.Конечно, я мог бы просто сказать, что «виртуальные таблицы применимы только к нестатическим функциям», но это не главное. Дело в том, что при определении конструкций программирования важная часть семантики языка программирования основана на сужении контекста и процесса разрешения ссылок во всех различных способах обозначения символа в программе. Для большинства основных языков это относится только к идентификаторам, особой лексической категории, но на самом деле она более общая (например, операторы в C ++ или Prolog, любой тип символов в Scheme). Общим названием для этого вида отсрочки эталонного разрешения являетсяВажная часть семантики языка программирования основана на сужении контекста и процесса разрешения ссылок во всех возможных способах обозначения символа в программе. Для большинства основных языков это относится только к идентификаторам, особой лексической категории, но на самом деле она более общая (например, операторы в C ++ или Prolog, любой тип символов в Scheme). Общим названием для этого вида отсрочки эталонного разрешения являетсяВажная часть семантики языка программирования основана на сужении контекста и процесса разрешения ссылок во всех возможных способах обозначения символа в программе. Для большинства основных языков это относится только к идентификаторам, особой лексической категории, но на самом деле она более общая (например, операторы в C ++ или Prolog, любой тип символов в Scheme). Общим названием для этого вида отсрочки эталонного разрешения являетсяОбщим названием для этого вида отсрочки эталонного разрешения являетсяОбщим названием для этого вида отсрочки эталонного разрешения является 
Позднее связывание . И всем это нравится, потому что «это более гибко». Хороший.

Затворы

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

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