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

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

Строка 1: Строка 1:
Visual Prolog best practice contains style suggestions and recommendations as used by Prolog Development Center (PDC), when writing programs in Visual Prolog.
Здесь предлагаются рекомендации по стилю программирования на Visual Prolog, выработанные в компании Prolog Development Center (PDC).


The description is intended for use with new Visual Prolog code, and PDC does not expect to update existing code to follow these standards. Therefore, you might see code in Visual Prolog distributions that does not follow these standards.
Эти рекомендации относятся к вновь создаваемым кодам. При этом PDC не планирует доведение уже существующих кодов до соответствия этим рекомендациям. Поэтому коды, распространяемые с системой программирования Visual Prolog могут не соответствовать приведенным стандартам.
 
==General Tips==
 
*The clauses of a predicate should normally occupy less than 20 lines. Otherwise you should consider introducing auxiliary predicates to handle sub-tasks. Situations, where a predicate can occupy more than 20 lines, are the cases, where e.g. 50 properties have to be set for some object each setting requiring a call.
 
*Use fully qualified names (e.g. someClass::method), whenever that makes the program more clear.
 
*Class methods should have meaningful names, when read together with the class name. Avoid repeating the class name in the method name, i.e. the name should be:


==Общие типы==
*Клаузы предикатов должны занимать, как правило, менее 20 строк. Поэтому необходимо предусматривать дополнительные предикаты для организации подпрограмм. Ситуации, где предикат может занимать более 20 строк, относятся к таким случаям, как, например, 50 свойств должны быть установлены для одного и того же объекта путем обращений к нему.
*Используйте полностью определенные имена (т.е. someClass::method), - это всегда далает программу более ясной.
*Методы класса должны иметь значимые имена, читаемые совместно с именем класса. Избегайте повторения имени класса в имени метода, т.е. имя должно быть:
<vip>someClass::method</vip>
<vip>someClass::method</vip>
 
а не
rather than
 
<vip>someClass::someClassMethod</vip>
<vip>someClass::someClassMethod</vip>
*Предикат должен выполнять точно одну задачу. Поэтому, если у Вас есть предикат, который что-то делает с каждым элементом списка, рассмотрите возможность разбиения его на два предиката: один делает проход по списку, а второй выполняет операцию над элементом. Достроинство такого подхода заключается в том, что предикат становится проще (и, следовательно, проще для модификации и понимания).
*В общем случае, если с одной стороны от знака равенства ("=") должна быть переменная, то помещайте ее '''''справа''''', если она '''''связана'''''.


*A predicate should only do one thing. So, if you have a predicate that does something with every element in a list, consider splitting it into two predicates: one that goes through the list and one that does something with the element. The advantage of doing this is that the predicates become simpler (and thus easier to get correct and easier to understand).
==Булевы значения==
 
Булевы значения следует использовать, если что-то либо истино, либо ложно. Не следует использовать такие значения для установления различий случаев, таких как слева и справа, или горизонтально и вертикально. В таких случаях лучше использовать специализированне домены.
*In general if one side of "=" is a variable then: put it '''''right''''' if it is '''''bound'''''.
 
==Boolean==
 
You should only use a Boolean if you have something which is either true or false. You should not use it to distinguish between two different cases like left or right, or horizontal or vertical. In these cases you should use specialized domains instead.
 
<vip>domains
<vip>domains
    direction = left(); right()
  direction = left(); right()
    orientation = horizontal(); vertical()</vip>
  orientation = horizontal(); vertical()</vip>
Если у Вас есть булевские переменные, то именуйте их для условия '''''истина'''''.  Так, переменная, выражающая то, что что-то '''опубликовано''' если она имеет значение '''истина (true)''' и ''не опубликовано'', если она имеет значение '''ложь (false)''', должна называться '''Опубликовано'''.


When you have a Boolean variable name it after the "true"-condition.  Thus, a variable that expresses that something is ''published'' if it is '''true''' and not ''published'' if it is '''false''' should be called '''Published'''.
==Отсечение (Cut)==
Отсечение (т.е. !) есть предикат, который  "отсекает (cuts)" недетирминизм, то есть отсекает возможность выработки дальнейших решений (сообразно своему имени).


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


Cut (i.e. !) is a predicate which "cuts" away nondeterminism, i.e. "cuts" away the possibility for further solutions (hence the name).
Других значимых причин для использования отсечений нет, кроме двух, упомянутых выше. Если эти цели понятны, то очень просто ставить отсечения в правильном месте:


The nondeterminism it cuts away can be divided into two groups (though some cuts will fall into both groups):
*либо отсечение ставится в месте, где перебор последовательных клауз больше не требуется и/или
 
*оно ставится после вызова недетерминированного (то есть с квалификацией nondeterm или multi) предиката, для которого важно только одно решение.
*Cut's that cut away the possibility to backtrack into a subsequent clause of the current predicate.
 
*Cut's that cut away further solutions to a nondeterministic predicate call.
 
There are no other (sensible) uses of a cut, except for the two mentioned above. Once these purposes are understood it is easy to put cuts in the right place:
 
*Either the cut is put at a place where backtrack to subsequent clauses is no longer wanted, or/and
 
*It is put after the call of a nondeterministic (i.e. nondeterm or multi) predicate call, for which only a single solution is important.
 
The first purpose can be illustrated by the following example:


Первая причина может быть проиллюстрирована следующим примером
<vip>clauses
<vip>clauses
     p(17, X) :-
     p(17, X) :-
Строка 58: Строка 42:
         ...</vip>
         ...</vip>


In this example we have a cut after the test for X > 13. This is a very typical sound use of the first reason: "Our clauses cases out on the input and '''now''' (where ''now'' is '''immediately''' after the test X > 13), we have found the right case".
В этом примере у нас есть отсечение после проверки X > 13. Это типичный случай использования первой причины: "Наши клаузы реагируют на значение входной переменной и '''сразу''' (где ''сразу'' означает '''немедленно''' после проверки X > 13) мы находим правильное решение".
 
Such a cut is typically placed just after the head of the clause or after a test close to the head of the clause.


The second purpose can be illustrated by the following example:
Обычно такого рода отсечения помещаются сразу после головы клаузы или после проверки, ближайшей к голове клаузы.


Вторая причина может быть проиллюстрирована на следующем примере:
<vip>clauses
<vip>clauses
    firstMember(X, L) :-
  firstMember(X, L) :-
        X = list::getMember_nd( L),
  X = list::getMember_nd( L),
        !.</vip>
  !.</vip>
 
В этом примере отсечение помещается '''немедленно''' после недетерминированного предиката, от которого мы ожидаем единственное решение.
In this example the cut is placed '''immediately''' after a nondeterministic predicate, of which we are only interested in a single solution.


Above I have highlighted the word ''immediately'' twice, and this is because the keyword in placing cuts is immediately: they should be placed as early in the clause as possible.
Мы выделили здесь слов '''немедленно''', поскольку ключевым словом в размещении  отсечения является слово '''немедленно''': они должны быть помещены настолько рано в клаузе, насколько это возможно.


You should be suspicious about clauses containing more that one cut. More than one cut in a single clause often signals a programming or design error.
Вы должны с подозрением относиться к клаузам, содержащим более одного отсечения. Наличие более одного отсечения в одном клаузе часто говорит о наличии ошибки в программе.


===Red and Green Cuts===
===Red and Green Cuts===

Версия 09:21, 25 октября 2007

Здесь предлагаются рекомендации по стилю программирования на 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)" недетирминизм, то есть отсекает возможность выработки дальнейших решений (сообразно своему имени).

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

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

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

  • либо отсечение ставится в месте, где перебор последовательных клауз больше не требуется и/или
  • оно ставится после вызова недетерминированного (то есть с квалификацией 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),
  !.

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

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

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

Red and Green Cuts

We do not generally encourage that cuts are turned green; it is perfectly fine with red cuts.

The society of traditional Prolog, has defined the notion of red and green cuts. Briefly speaking a green cut is a cut, which does not change the semantics of the predicate in which it occurs, whereas a red one does change the semantics.

Clearly all the cuts which cut away further solutions from a nondeterministic predicate are red by nature. So distinguishing between red and green cuts only has a purpose for those cuts, which prevents backtracking to next clauses.

Consider the clauses:

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

The cut in the predicate above is green, because if we remove it the predicate will behave in the same way. When the cut is present in the first clause, the test X <= 0 in the second clause is not really needed:

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

Without this test the cut is, however, turned red, because now the predicate will behave different if we remove the cut.

Green cuts might seem superfluous, but in fact they are used to remove superfluous backtrack points (mainly for performance reasons). In Visual Prolog they may however also be needed to "convince" the compiler that some predicate has a specific mode (e.g. procedure).

Dispatching on Input Patterns

Only handle very simple things in a dispatcher. The problem with handling stuff in a dispatcher is that it has a very flat structure and typically a lot of different things has to happen. So code that belongs logically together gets spread and pieces of code that basically has very little to do with each other stands next to each other.

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.
  • Never have more clauses for the "same case", if the predicate also handles "other" cases.

The first "rule" is straight forward. The second rule can be illustrated with the following example:

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
    ...

The clauses section above represents a bad coding style, because it has two clauses for the same input "pattern". This would have been OK if this was the only input pattern the predicate deals with. But in this case, we have clauses for other patterns as well. I think that the predicate above should have been rewritten, such that the purpose of qwerty is solely to case out on the specific patterns of input leaving other work to sub-predicates:

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

This code has changed the qwerty predicate into a predicate, which only cases-out on the various combinations of input. It is not essential that you only have a single predicate call for each kind of input as illustrated above. The main point is that you do not back-track from one clause to another on the same input pattern.

This rule does especially apply to event handlers, you should not have more than one clauses for "close" (or anything else) in the same event handler. An event handler often spread on several pages, so if it is not a rule that each case is handled by a single clause, then you will have to look at all clauses to be sure that you know how a single input pattern is handled.

Exceptions and Error Handling

  • 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

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.
  • 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.

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:

  • If an index is out of limits
  • If a window handle is "wrong"
  • If the predicates are called in the wrong order.

There are two reasons why one might want to trap an exit:

  • 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.
  • 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.

References