Стиль программирования: различия между версиями

Материал из wikiru.visual-prolog.com

м (Исправление орфографической ошибки)
 
(не показано 12 промежуточных версий 3 участников)
Строка 10: Строка 10:
а не  
а не  
<vip>someClass::someClassMethod</vip>
<vip>someClass::someClassMethod</vip>
*Предикат должен выполнять точно одну задачу. Поэтому, если у Вас есть предикат, который что-то делает с каждым элементом списка, рассмотрите возможность разбиения его на два предиката: один делает проход по списку, а второй выполняет операцию над элементом. Достроинство такого подхода заключается в том, что предикат становится проще (и, следовательно, проще для модификации и понимания).
*Предикат должен выполнять точно одну задачу. Поэтому, если у Вас есть предикат, который что-то делает с каждым элементом списка, рассмотрите возможность разбиения его на два предиката: один делает проход по списку, а второй выполняет операцию над элементом. Достоинство такого подхода заключается в том, что предикат становится проще (и, следовательно, проще для модификации и понимания).
*В общем случае, если с одной стороны от знака равенства ("=") должна быть переменная, то помещайте ее '''''справа''''', если она '''''связана'''''.
*В общем случае, если с одной стороны от знака равенства ("=") должна быть переменная, то помещайте ее '''''справа''''', если она '''''связана'''''.


Строка 92: Строка 92:
==Управление с использованием комбинаций входных данных==
==Управление с использованием комбинаций входных данных==


Управление с использованием комбинаций входных данных следует использовать с осторожностью. Проблема использьования входных потоков заключается в том, что эта прострая структура может реагировать на множество различных ситуаций. Поэтому коды, которые логически сильно взаимосвязаны, следует разбивать на сравнительно независимые фрагменты, и выполнять их последовательно.
Управление с использованием комбинаций входных данных (диспетчирование) следует использовать с осторожностью. Проблема использования входных потоков заключается в том, что эта простая структура может реагировать на множество различных ситуаций. Поэтому коды, которые логически сильно взаимосвязаны, следует разбивать на сравнительно независимые фрагменты, и располагать их последовательно.


При этом логически связанные коды помещаются в обрабатывающие предикаты, так что диспетчер вызывает только обрабатывающие предикаты. To avoid this code that belongs logically together should be placed in handling predicates that are grouped in a module so the dispatcher only calls the handling predicates. This way the dispatcher is just that a dispatcher and code that belongs together are placed close together.
При этом логически связанные коды помещаются в обрабатывающие предикаты, расположенные в том же классе-модуле, так что диспетчер вызывает только их. При этом сам диспетчер и обрабатывающие коды остаются в непосредственной близости.
 
*Если предикат обрабатывает много вариантов входных наборов данных, поддерживайте каждый такой вариант простым. If a predicate handles many cases then keep each case simple.


Для такого рода управления рекомендуется следовать правилам:
*Если предикат обрабатывает много вариантов входных наборов данных, поддерживайте каждый такой вариант простым.
*Никогда не создавайте несколько клауз для одного и того же варианта входных данных, если предикат обрабатывает также "другие" случаи.
*Никогда не создавайте несколько клауз для одного и того же варианта входных данных, если предикат обрабатывает также "другие" случаи.


Строка 140: Строка 140:
Это правило особенно полезно при построении обработчиков событий - у Вас не должно быть более одного клауза для события "close" (или подобного) в том же самом обработчике. Обработчик событий часто располагается на нескольких страницах экрана, поэтому если не следовать правилу обработки одного вида входных наборов одним клаузом, то Вам необходимо будет просматривать все клаузы обработчика, чтобы выяснить как обрабатывется конкретный входной набор.
Это правило особенно полезно при построении обработчиков событий - у Вас не должно быть более одного клауза для события "close" (или подобного) в том же самом обработчике. Обработчик событий часто располагается на нескольких страницах экрана, поэтому если не следовать правилу обработки одного вида входных наборов одним клаузом, то Вам необходимо будет просматривать все клаузы обработчика, чтобы выяснить как обрабатывется конкретный входной набор.


==Exceptions and Error Handling==
==Исключения и обработка ошибок==
 
*Когда происходит ошибка или исключительная ситуация, возбудите исключительную ситуацию с помощью ''exception::raise''.
*Блокируйте (trap) исключения если Вы хотите их обрабатывать.
*Используйте ''finally'' если необходимо выполнить некоторый код даже при возникновении исключения, и уже затем продолжите исключение.
*Ознакомьтес с руководством по обработке исключений.


*When an error/exception occurs, raise an exception with ''exception::raise''.
===Внутренние и прочие ошибки===
Следует различать внутренние ошибки и ошибки, связанные с использованием пакета, модуля, класса, компоненты. Если нарушается внутренняя инвариантность, то это внутренняя ошибка. Типичными примерами внутренних ошибок являются:
*Факт базы данных, который должен быть определён, не определён на самом деле.
*Предикат, который должен бы быть процедурой, но компилятор не способен это распознать, преобразован в процедуру путём добавления "холостого" клауза. Если такой клауз достигается, то это происходит от того, что предположение о том, что предыдущий клауз не может завершиться не успешно, было неверным.


*Trap an exception if you want to handle the exception.
Внутренние ошибки должны генерировать по одному исключению на каждую. Использование исключений следует по принципу: по одному на каждую ошибку пользователя и по одному на каждую внутреннюю ошибку.


*Use ''finally'' if you want to run some code even in the exception case and then continue the exception.
Типичные пользовательские ошибки:


*See also the tutorial about Exceptions handling.
*Выход индекса за допустимый диапазон
*"неправильный" указатель на окно
*нарушение порядка следования предикатов


===Internal Errors and Other Errors===
Блокировка (try/catch) завершения программы может быть предусмотрена по двум причинам:
*Есть желание обработать ''исключение''. Скажем, при открытии файла возникает прерывание, сообщающее, что файл не существует. В этом случает возникает желание блокировать завершение программы и показать некоторое сообщение об ошибке, говорящее о том, что файл не существует. Естественно, если такое исключение не является единственно возможным, то следует продолжить исключение.
*Есть желание что-то сделать независимо от того, прерывается ли выполнение предиката. Типичный пример - Вы что-блокируете (захватываете ресурс), затем что-то делаете и, наконец, сбрасываете блокировку (освобождаете ресурс). Здесь Вам надо быть уверенным, что блокировка сброшена (ресурс освобождён), даже если выполнение прерывается. С этой целью используется ''try/finally''.


You should distinguish between internal errors and errors which had to do with the usage of the tool/module/class/unit. If some internal invariant is violated it is an internal error. Typical examples of internal errors are:
==Типы и потоки ввода-вывода==


*A database fact which should have been defined is not defined.
По умолчанию предикаты имеют тип '''procedure (процедуры)'''.  Не стоит писать квалификатор '''procedure''' до тех пор, пока в этом не возникнет необходимость (но, возможно, в этом случае следует рассмотреть необходимость изменения имени предиката).


*A predicate which should be a procedure, but which the compiler could not recognize as such are made into a procedure by adding a fall through clause, if that clause is reached it is because the assumption that the previous clause could not fail (an invariant) was wrong.
<vip>predicates
  doSomeThing : (). % не пишите здесь "procedure"</vip>


Internal errors should share one exception per unit. You should always use an exception: One for each user error and one for internal error (that is one for each unit).
По умолчанию все аргументы предикатов являются входными. Неиспользуемые комбинации потоков ввода-вывода писать не следует.


Typical user errors:
<vip>predicates
  doSomeThing : (string In1, string In2).
  % не пишите здесь потоки ввода-вывода, если все потоки являются и так входными</vip>


*If an index is out of limits
Непосредственно с аргументами могут быть использованы квалификаторы '''[in]''' и '''[out]''' . Не пишите '''[in]''', поскольку аргументы являются входными по умолчанию.


*If a window handle is "wrong"
<vip>predicates
  doSomeThing : (string In1 /* не писать здесь [in]*/, string Out2 [out]).</vip>


*If the predicates are called in the wrong order.
Если придикат имеет только один вариант потоков входа-выхода, надо использовать '''[out]''' вместо шаблона.


There are two reasons why one might want to trap an exit:
<vip>predicates
  doSomeThing : (string In1 /* не писать здесь [in]*/, string Out2 [out]).
  % не писать шаболоны ввода-вывода, если вариант всего один</vip>


*Because one wants to handle the ''exception'', say you open a file and get an exit saying that the file does not exists. In that case you want to trap the exit and display some error message saying that the file does not exists. Of course if the exception was not one of the ones you wanted to handle you have to continue the exception.
По умолчанию факты являются недетерминированными - '''nondeterm'''.  Не следует писать '''nondeterm''', если в этом нет особой необходимости (но следует, возможно, рассмотреть изменение имени факта).


*Because you want to do something regardless of whether the predicate exits, a typical example is that you get some kind of lock do something and release the lock. Here you want to be sure that the lock is released also if this something exits. So you use ''finally''.
<vip>facts
  personData_fact : (string FirstName, string LastName, string Address).  
  % не писать здесь "nondeterm"</vip>


==References==
==Ссылки==
[[en:Programming Pragmatics]]
[[en:Programming Pragmatics]]
[[Category:VipLanguage]]
[[Category:VipLanguage]]
[[Category:VipРуководства]]
[[Category:VipРуководства]]

Текущая версия на 16:04, 31 января 2022

Здесь предлагаются рекомендации по стилю программирования на Visual Prolog, выработанные в компании Prolog Development Center (PDC).

Эти рекомендации относятся к вновь создаваемым кодам. При этом PDC не планирует доведение уже существующих кодов до соответствия этим рекомендациям. Поэтому коды, распространяемые с системой программирования Visual Prolog могут не соответствовать приведенным стандартам.

Общие рекомендации

  • Клаузы предикатов должны занимать, как правило, менее 20 строк. Поэтому необходимо предусматривать дополнительные предикаты для организации подпрограмм. Ситуации, где предикат может занимать более 20 строк, относятся к таким случаям, как, например, 50 свойств должны быть установлены для одного и того же объекта путем обращений к нему.
  • Используйте полностью определенные имена (т.е. someClass::method), - это всегда далает программу более ясной.
  • Методы класса должны иметь значимые имена, читаемые совместно с именем класса. Избегайте повторения имени класса в имени метода, т.е. имя должно быть:
someClass::method

а не

someClass::someClassMethod
  • Предикат должен выполнять точно одну задачу. Поэтому, если у Вас есть предикат, который что-то делает с каждым элементом списка, рассмотрите возможность разбиения его на два предиката: один делает проход по списку, а второй выполняет операцию над элементом. Достоинство такого подхода заключается в том, что предикат становится проще (и, следовательно, проще для модификации и понимания).
  • В общем случае, если с одной стороны от знака равенства ("=") должна быть переменная, то помещайте ее справа, если она связана.

Булевы значения

Булевы значения следует использовать, если что-то либо истино, либо ложно. Не следует использовать такие значения для установления различий случаев, таких как слева и справа, или горизонтально и вертикально. В таких случаях лучше использовать специализированне домены.

domains
  direction = left(); right()
  orientation = horizontal(); vertical()

Если у Вас есть булевские переменные, то именуйте их для условия истина. Так, переменная, выражающая то, что что-то опубликовано если она имеет значение истина (true) и не опубликовано, если она имеет значение ложь (false), должна называться Опубликовано.

Отсечение (Cut)

Отсечение (т.е. !) есть предикат, который отсекает (cuts) недетерминизм, то есть отсекает возможность выработки дальнейших решений (сообразно своему имени).

Отсекаемый недетерминизм можно разбить на два группы (хотя в ряде случаев отсечения могут принадлежать обеим):

  • Отсечения, которые отсекают возможность выполнения других (следующих по порядку) клауз текущего предиката.
  • Отсечения, которые ликвидируют возможность генерации новых решений у недетерминированных вызовов предикатов.

Других значимых причин для использования отсечений нет, кроме двух, упомянутых выше. Если эти цели понятны, то очень просто ставить отсечения в правильном месте:

  • либо отсечение ставится в месте, где перебор последовательных клауз больше не требуется и/или
  • оно ставится после вызова недетерминированного (то есть с квалификацией nondeterm или multi) предиката, для которого важно только одно решение.

Первая причина может быть проиллюстрирована следующим примером

clauses
    p(17, X) :-
        X > 13,
        !,
        q(X),
        ...
    p(A, X) :-
        ...

В этом примере у нас есть отсечение после проверки X > 13. Это типичный случай использования первой причины: "Наши клаузы реагируют на значение входной переменной и сразу (где сразу означает немедленно после проверки X > 13) мы находим правильное решение".

Обычно такого рода отсечения помещаются сразу после головы клаузы или после проверки, ближайшей к голове клаузы.

Вторая причина может быть проиллюстрирована на следующем примере:

clauses
  firstMember(X, L) :-
  X = list::getMember_nd( L),
  !.

В этом примере отсечение помещается немедленно после недетерминированного предиката, от которого мы ожидаем единственное решение.

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

Вы должны с подозрением относиться к клаузам, содержащим более одного отсечения. Наличие более одного отсечения в одном клаузе часто говорит о наличии ошибки в программе.

Красные и Зеленые отсечения

Вообще говоря, мы не поощряем зеленые отсечения, мы вполне уживаемся с красными отсечениями.

Сообщество традиционного Пролога дало определение красного и зеленого отсечений. Коротко это: зеленое отсечение - это отсечение, которое не меняет семантику предиката, в котором используется, а красное отсечение - меняет семантику.

Ясно, что все отсечения, которые отсекают все последующие решения недетерминированных предикатов являются красными по своей природе. Поэтому различия между красными и зелеными отсечениями имеют смысл только для тех отсечений, которые предотвращают откаты к следующим клаузам.

Рассмтрим клаузы:

clauses
    p(X) :-
        X > 0,
        !,
        ...
    p(X) :-
        X <= 0,
        ...

В предикате выше отсечение является зеленым (green), поскольку, если мы его удалим, то предикат будет вести себя таким же образом. Когда отсечение имеется в первом клаузе, проверка X <= 0 во втором клаузе в действительности не нужна (второй клауз соответствует всем остальным случаям, кроме X<0 - прим. Переводчика):

clauses
    p(X) :-
        X > 0,
        !,
        ...
    p(X) :-
        ...

Но без этой проверки, однако, отсечение становится красным, поскольку теперь предикат будет вести себя по-другому, если мы удалим отсечение (предикат становится недетерминированным, поскольку, даже если X>0, появляется еще одно решение - хорошо, если безвредное - прим. Переводчика).

Зеленые отсечения могут показаться излишними, но в действительности они используются для исключения лишних вариантов отката (главным образом по соображениями скорости работы). В системе Visual Prolog они могут, однако, быть необходимыми для того, чтобы показать компилятору, что некоторые предикаты являются предикатами определенного типа, например, процедурами.

Управление с использованием комбинаций входных данных

Управление с использованием комбинаций входных данных (диспетчирование) следует использовать с осторожностью. Проблема использования входных потоков заключается в том, что эта простая структура может реагировать на множество различных ситуаций. Поэтому коды, которые логически сильно взаимосвязаны, следует разбивать на сравнительно независимые фрагменты, и располагать их последовательно.

При этом логически связанные коды помещаются в обрабатывающие предикаты, расположенные в том же классе-модуле, так что диспетчер вызывает только их. При этом сам диспетчер и обрабатывающие коды остаются в непосредственной близости.

Для такого рода управления рекомендуется следовать правилам:

  • Если предикат обрабатывает много вариантов входных наборов данных, поддерживайте каждый такой вариант простым.
  • Никогда не создавайте несколько клауз для одного и того же варианта входных данных, если предикат обрабатывает также "другие" случаи.

Первое правило является правилом прямого действия. Второе правило может быть проиллюстрировано на следующем примере:

clauses
  qwerty(17, W, E, R, 13, Y) :-
    ..., % A
    !,
    ... % B
  qwerty(17, W, E, R, 13, Y) :-
    ..., % C
  qwerty(Q, W, E, R, 13, Y) :-
    ... % D
...

Секция clauses выше написана в плохом стиле, поскольку имеет две клаузы для одного и того же входного набора (17, W, E, R, 13, Y). Это было бы не страшно, если бы предикат работал бы только с этим набором. Но здесь есть клаузы и для других наборов. Мы полагаем, что такой предикат должет быть переписан таким образом, чтобы каждый клауз предиката qwerty реагировал бы только на специальный входной набор, оставляя остальную обработку предикатам более низкого уровня:

clauses
    qwerty(17, W, E, R, 13, Y) :-
        !, % we have cased out, this is one of our cases
        qwerty_17_w_e_r_13_y(W, E, R, Y).
    qwerty(Q, W, E, R, 13, Y) :-
        !, % we have cased out, this is one of our cases
        qwerty_q_w_e_r_13_y(Q, W, E, R, Y).
    ...
clauses
    qwerty_17_w_e_r_13_y(W, E, R, Y) :-
        ..., % A
        !,
        ... % B
    qwerty_17_w_e_r_13_y(W, E, R, Y) :-
        ... % C
clauses
    qwerty_q_w_e_r_13_y(Q, W, E, R, Y) :-
        ... % D

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

Это правило особенно полезно при построении обработчиков событий - у Вас не должно быть более одного клауза для события "close" (или подобного) в том же самом обработчике. Обработчик событий часто располагается на нескольких страницах экрана, поэтому если не следовать правилу обработки одного вида входных наборов одним клаузом, то Вам необходимо будет просматривать все клаузы обработчика, чтобы выяснить как обрабатывется конкретный входной набор.

Исключения и обработка ошибок

  • Когда происходит ошибка или исключительная ситуация, возбудите исключительную ситуацию с помощью exception::raise.
  • Блокируйте (trap) исключения если Вы хотите их обрабатывать.
  • Используйте finally если необходимо выполнить некоторый код даже при возникновении исключения, и уже затем продолжите исключение.
  • Ознакомьтес с руководством по обработке исключений.

Внутренние и прочие ошибки

Следует различать внутренние ошибки и ошибки, связанные с использованием пакета, модуля, класса, компоненты. Если нарушается внутренняя инвариантность, то это внутренняя ошибка. Типичными примерами внутренних ошибок являются:

  • Факт базы данных, который должен быть определён, не определён на самом деле.
  • Предикат, который должен бы быть процедурой, но компилятор не способен это распознать, преобразован в процедуру путём добавления "холостого" клауза. Если такой клауз достигается, то это происходит от того, что предположение о том, что предыдущий клауз не может завершиться не успешно, было неверным.

Внутренние ошибки должны генерировать по одному исключению на каждую. Использование исключений следует по принципу: по одному на каждую ошибку пользователя и по одному на каждую внутреннюю ошибку.

Типичные пользовательские ошибки:

  • Выход индекса за допустимый диапазон
  • "неправильный" указатель на окно
  • нарушение порядка следования предикатов

Блокировка (try/catch) завершения программы может быть предусмотрена по двум причинам:

  • Есть желание обработать исключение. Скажем, при открытии файла возникает прерывание, сообщающее, что файл не существует. В этом случает возникает желание блокировать завершение программы и показать некоторое сообщение об ошибке, говорящее о том, что файл не существует. Естественно, если такое исключение не является единственно возможным, то следует продолжить исключение.
  • Есть желание что-то сделать независимо от того, прерывается ли выполнение предиката. Типичный пример - Вы что-блокируете (захватываете ресурс), затем что-то делаете и, наконец, сбрасываете блокировку (освобождаете ресурс). Здесь Вам надо быть уверенным, что блокировка сброшена (ресурс освобождён), даже если выполнение прерывается. С этой целью используется try/finally.

Типы и потоки ввода-вывода

По умолчанию предикаты имеют тип procedure (процедуры). Не стоит писать квалификатор procedure до тех пор, пока в этом не возникнет необходимость (но, возможно, в этом случае следует рассмотреть необходимость изменения имени предиката).

predicates
  doSomeThing : ().  % не пишите здесь "procedure"

По умолчанию все аргументы предикатов являются входными. Неиспользуемые комбинации потоков ввода-вывода писать не следует.

predicates
  doSomeThing : (string In1, string In2).
  % не пишите здесь потоки ввода-вывода, если все потоки являются и так входными

Непосредственно с аргументами могут быть использованы квалификаторы [in] и [out] . Не пишите [in], поскольку аргументы являются входными по умолчанию.

predicates
  doSomeThing : (string In1 /* не писать здесь [in]*/, string Out2 [out]).

Если придикат имеет только один вариант потоков входа-выхода, надо использовать [out] вместо шаблона.

predicates
  doSomeThing : (string In1 /* не писать здесь [in]*/, string Out2 [out]).
  % не писать шаболоны ввода-вывода, если вариант всего один

По умолчанию факты являются недетерминированными - nondeterm. Не следует писать nondeterm, если в этом нет особой необходимости (но следует, возможно, рассмотреть изменение имени факта).

facts
  personData_fact : (string FirstName, string LastName, string Address). 
  % не писать здесь "nondeterm"

Ссылки