style
.pdfATTRIBUTES….: Method’s attributes
ARGUMENTS…..: Arguments of the method, their type and meaning. RETURNS…....: Possible return values and their meaning NOTES…......: For instance, explanation of the usage and/or an
example, limitations, pseudocode, etc. \****************************************************************
/
Поля METHOD, DESCRIPTION, ARGUMENTS, RETURNS являются обяза-
тельными, остальные могут отсутствовать.
Каждому аргументу метода может предшествовать один из следующих префиксов:
• IN – означает, что данный параметр – только для чтения и изменению не подлежит;
•OUT – означает, что данный параметр будет изменен при выполнении метода, однако его начальное значение методом не используется;
•INOUT - означает, что данный параметр будет изменен при выполнении метода, а его начальное значение методом используется.
Однако не все компиляторы их поддерживают (например, MS Visual C++ их не поддерживает). Поэтому предпочтительно использовать данные слова в in-line комментариях.
Пример:
/****************************************************************
\
METHOD........: Read
DESCRIPTION...: Reads nCount bytes to pBuffer beginning from the given file position.
ATTRIBUTES....: |
Public |
ARGUMENTS.....: |
fpStart - the file position to be read from, |
ceive |
pBuffer |
- pointer to a buffer that is to re- |
|
read data from the file, |
|
|
nCount |
|
RETURNS |
- maximum bytes to be read. |
|
: Returns the number of bytes read. If the method |
||
|
tries to read at end of file, it returns 0. |
|
|
If the reading failed, the method return -1. |
|
|
To get extended error information, call |
|
|
GetLastError() method. |
|
NOTES.........: The following error codes GetLastError() can return after
the method failed:
ERR_MEMORY |
- not enough |
memory, |
ERR_FILELOCK - the file is |
locked to read |
|
\****************************************************************
/
int CDiskMngr::Read
31
( |
*/ |
FilePtr fpStart, |
// Начало |
|
/* IN |
||||
/* OUT */ |
void* |
pBuffer, |
// Адрес буфера |
|
/* IN |
*/ |
int |
nCount |
// Счетчик |
) |
|
|
|
|
{ |
|
|
|
|
...
}
4.9.Функции
•Заголовок функции представляет собой закомментированный текст, предшествующий определению функции. Он имеет следующий вид:
/****************************************************************
\
FUNCTION...... |
: Name of the function. |
DESCRIPTION... |
: Purpose of the function. |
ARGUMENTS..... |
: Arguments of the function, their type and |
RETURNS |
meaning. |
: Possible return values and their meaning. |
|
EXTERNS....... |
: Global data used/affected in the function. |
NOTES......... |
: For instance, explanation of the usage and/or |
an |
|
example, limitations, pseudocode, etc. \****************************************************************
/
Поля FUNCTION, DESCRIPTION, ARGUMENTS, RETURNS являются обя-
зательными, остальные могут отсутствовать.
•Каждому аргументу метода может предшествовать один из следующих префиксов:
• IN – означает, что данный параметр – только для чтения и изменению не подлежит.
•OUT – означает, что данный параметр будет изменен при выполнении метода, однако его начальное значение методом не используется.
•INOUT - означает, что данный параметр будет изменен при выполнении метода, а его начальное значение методом используется.
Однако не все компиляторы их поддерживают (например, MS Visual C++ их не поддерживает). Поэтому предпочтительно использовать данные слова в in-line комментариях.
Пример:
/****************************************************************
\
FUNCTION...... |
: AbbreviateFileName |
DESCRIPTION... |
: Makes an abbreviate file name from the given |
|
Full path name. |
32
ARGUMENTS.....: lpszCanon |
- pointer to a full path name to |
be |
|
abbreviated,
ichMax - max number of characters of the resultant file name,
bAtLeastName - specify whether the file name should be left at least after abbreviation.
RETURNS.......: |
None. |
NOTES.........: |
Below is the example how the function works. |
lpszCanon = “C:\MYAPP\DEBUGS\C\TESWIN.C”; ichMax bAtLeastName Result (lpszCanon)
------- ------------ ---------------------------
1-7 |
FALSE |
<empty> |
1-7 |
TRUE |
TESWIN.C |
8-14 |
x |
TESWIN.C |
15-16 |
x |
C:\...\TESWIN.C |
17-23 |
x |
C:\...\C\TESWIN.C |
24-25 |
x |
C:\...\DEBUGS\C\TESWIN.C |
26+ |
x |
C:\MYAPP\DEBUGS\C\TESWIN.C |
\****************************************************************
/
void PASCAL AbbreviateFileName
(
/* INOUT */ |
LPTSTR |
lpszCanon, |
|
/* IN |
*/ |
int |
ichMax, |
/* IN |
*/ |
BOOL |
bAtLeastName |
)
{
...
}
4.10. Документирование каталогов
Каждый каталог должен содержать README файл, в котором описано:
•Назначение каталога и его содержимое.
•Одна строка комментария на каждый файл. Комментарий обычно извлекается из поля NAME оглавления файла.
•Описание процедуры инсталляции.
•Перечень ресурсов и ссылки на них:
•каталоги исходных файлов;
•оперативная документация (справочная система и др. доступные в электронной форме руководства);
•перечень бумажных документов (т.е. документов, представленных не в электронной форме).
33
4.11.Требования к комментариям
•Комментарии должны формулироваться таким образом, чтобы, если их извлечь из кода программы, они составили осмысленное связное изложение – описание функционирования программы.
•Комментарии должны документировать принимаемые вами решения (что вы делаете в данном месте программы и почему).
•Комментарии должны быть лаконичными и понятными. Встроенные ключевые слова (gotchas) можно использовать, чтобы отметить места, чреватые проблемами.
•Gotcha Keywords – ключевые слова в комментарии.
:TODO: topic. Означает: ЗДЕСЬ НАДО ДОРАБОТАТЬ. НЕ ЗАБУДЬ.
:BUG: [bugid] topic. Означает: ЗДЕСЬ НАХОДИТСЯ ИЗВЕСТНЫЙ
BUG. Объясните его причину и присвойте ему идентификатор (bug ID)
•:KLUDGE: Если вы сделали что-то неудачное, объясните, как это произошло, и что вы намерены делать впредь, чтобы избежать этого в будущем.
•:TRICKY: Предупреждение, что следующая часть программы очень сложная, ее не следует изменять без тщательного обдумывания.
•:WARNING: Предупреждение о чем-либо.
•:COMPILER: Иногда требуется решить проблему с компилятором. Задокументируйте ее. Проблема может быть решена позднее.
•:ATTRIBUTE: value. Общий вид атрибута, вставленного в комментарий. Вы можете задавать собственные атрибуты, которые затем могут быть извлечены из комментария.
Вдополнение вы можете использовать возможности препроцессора, но имейте в виду, что не все компиляторы их поддерживают.
#define chSTR(x) |
#x |
|
#define chSTR2(x) |
chSTR(x) |
\ |
#define TODO(desc) |
message(":TODO:("__FILE__", |
|
#define BUG(desc) |
"chSTR2(__LINE__) ") " #desc) |
|
message(":BUG:("__FILE__", \ |
|
|
|
"chSTR2(__LINE__) ") " #desc) |
|
Когда вы вставляете эти строки в ваш .h/.cpp – файл, вы можете использовать их следующим образом:
34
main() |
|
{ |
// in-line comment |
if (condition) |
|
{ |
|
#pragma TODO("Add error handling here")
...
}
else if (condition) // in-line comment
{
...
}
else // in-line comment
{
#pragma TODO("There is no error handling here")
...
}
}
Это позволит видеть все потенциальные проблемы программы во время ее компиляции.
•Форматирование кода программы при использовании ключевых слов
Gotcha.
•Ключевое слово Gotcha должно быть первым словом комментария.
•Комментарий может состоять из нескольких слов, но первое слово должно содержать смысл комментария, а последующие слова служат лишь для уточнения.
•Имя автора и дата внесения замечания должны быть частью комментария.
Часто Gotcha остаются в программе дольше, чем это необходимо. Данные о дате и авторе изменений позволят принять другим программистам решение. Зная имя автора, вы сможете связаться с ним и получить консультацию.
Пример:
//:TODO: tmh 960810: possible performance problem
//We should really use a hash table here but for now we'll
//use a linear search.
//:KLUDGE: tmh 960810: possible unsafe type cast
//We need a cast here to recover the derived type. It should
//probably use a virtual method or template.
35
5.ОПЕРАТОРЫ
5.1.Оператор if…else
•Если if завершается оператором return, то не используйте else. Код получается более понятным.
Так, вместо
if (condition) return 0; else
{
do_something();
}
лучше написать
if (condition) return 0; do_something();
Вэтом случае последним return может быть выдача кода ошибки (это позволит отловить «случайное» попадание в ненужную ветвь алгоритма).
•Необходимо стараться проверять все альтернативные возможности (в том числе все “предельные случаи”).
5.2.Циклы
•По возможности следует избегать циклов do…while. Они опасны тем, что тело цикла исполняется всегда хотя бы один раз. Кроме того, при чтении программы очень трудно отыскать проверяемое условие, т.к. while в такой конструкции располагается «внизу», т.е. в конце тела цикла. По этим причинам никогда не следует употреблять do…while для организации бесконечного цикла – для этой цели намного лучше использо-
вать while(1).
•Всегда используйте оператор for, если присутствуют любые два из инициализирующего, условного или инкрементного выражений. Это сокращает код и делает его более понятным.
5.3.Препроцессор
•Обязательно заключайте в скобки тела сложных макросов и их аргументы. Например, следует писать
36
#define TWO_K |
(1024+1024) |
//correct! |
а не |
1024+1024 |
//incorrect! |
#define TWO_K |
В последнем случае TWO_K*10 будет вычислено на этапе компиляции как 1024+1024*10=11264 вместо ожидаемого 20480.
Из приведенного примера видно, что макросы может быть достаточно трудно сопровождать. Поэтому:
•для задания констант, используемых в программе (кроме системозависимых параметров) по возможности следует использовать модификатор const или перечисление enum и избегать использования #define;
•вместо параметризованных макрооопределений #define лучше использовать inline-функции.
5.4. Константы и перечисления
Следует помнить, что const int на самом деле не задает константу, а резервирует память под int (хотя и запрещает менять содержащееся в ней значение). Под перечисление enum память никогда не выделяется, поэтому использование enum может быть предпочтительнее.
6. МОБИЛЬНОСТЬ
Мобильность программ – это свойство, позволяющее выполнять программы на разных компьютерах, под управлением разных операционных систем, с минимальными изменениями кода. Естественно, мобильность предусматривает и возможность трансляции программы разными компиляторами. Думать о мобильности нужно даже в том случае, если предполагается, что программа пишется, скажем, только «под» Windows NT и только с использованием MS Visual C++ (т.е. никогда не планируется использовать другой компилятор). Дело в том, что аппаратное и программное обеспечение не стоит на месте, и вполне возможно, что через несколько лет написанную вами программу придется полностью переделывать под новую версию «старой» операционной системы и, соответственно, под новый компилятор.
6.1. Зависимость от компилятора
Некоторые детали в C/C++ не стандартизованы. Например:
•не определен порядок вычисления операндов большинства бинарных операций, таких как сложение и умножение. Следовательно, не опреде-
37
лен порядок появления возможных побочных эффектов;
•некоторые директивы и ключевые слова (в основном это директивы препроцессора) поддерживаются только определенными компиляторами.
Не следует явно или неявно пользоваться нестандартными возможностями, предоставляемыми конкретным компилятором.
6.2.Зависимость от компьютера
•Не следует полагаться на определенный размер машинного слова, он может быть разным у разных компьютеров.
• Размеры данных базовых типов (int, float, double, char и т.п.) в байтах и в машинных словах могут быть разными у разных компьютеров и в разных операционных системах. Гарантировано только, что размер short не меньше размера int, размер float не меньше размера double и т.п. Размеры данных могут сказаться на обработке двоичных масок. Например, в фрагменте
#define MASK 0177770 // incorrect!
int x;
…
x &= MASK;
три правых бита целого x будут обнуляться только в случае, если данные типа int занимают 16 бит. Но если размер данных типа int больше 16 бит, то обнуляться будут левые биты x. Поэтому предпочтительнее
#define MASK (~07) // correct! int x;
…
x &= MASK;
•По возможности следует вообще избегать использования двоичных масок. Это рудимент “ассемблерного” стиля программирования. Лучше использовать битовые поля.
•Используйте sizeof() для определения размера объектов, в том числе и переменных базовых типов.
•Следует тщательно проверять операции двоичного сдвига. Максимальное число бит, которые могут быть сдвинуты вправо или влево, различается на разных компьютерах и в разных операционных системах.
•Максимальный размер битового поля зависит от размера машинного слова и, следовательно, не является стандартным.
•При работе с объектами разных типов данных (классов) используйте преобразование типа. Особенно важно использовать преобразование типов
38
для указателей.
•Используйте макроопределения #define для задания системозависимых именованных констант.
•Не полагайтесь на внутреннюю кодировку целых и вещественных типов данных. Например, не следует полагаться на использование дополнительного или обратного кода для представления целых типов.
•Не являются стандартными порядок и число байт в машинном слове, число бит в байте (данная константа определена в файле <values.h>).
•Не следует полагаться на конкретные значения основных констант, таких как NULL и EOF. Например, NULL, как правило, является константой 0, но это вовсе не обязательно. Поэтому
if ( lval == NULL ) { … }
лучше, чем
if ( !lval ) { … }
•Символы (char) могут иметь знак. Для повышения мобильности можно использовать явное описание unsigned char или преобразовывать символы перед обработкой к типу unsigned char.
•Нельзя полагаться на конкретную кодировку символов, в том числе на определенную последовательность символов в кодировке. Например,
if ( islower(ch) ) { … }
лучше, чем
if ( ch >= ‘a’ && ch <= ‘z’ ) { … }
так как использует стандартную библиотечную функцию islower(), а не полагается на предположение о последовательности строчных букв в кодировке.
•Для выделения/освобождения памяти лучше использовать new/delete,
а не malloc()/free().
7. ПРАВИЛЬНАЯ ОРГАНИЗАЦИЯ ПРОГРАММ
Программа организована правильно, если ее легко читать, модифицировать, эксплуатировать и, следовательно, переносить на другие компьютеры и в другое операционное окружение. Для правильной организации программ имеет смысл придерживаться следующих рекомендаций.
•Все определения, связанные с конкретным компьютером и/или операционной средой, следует оформлять при помощи директивы препроцессора #define и помещать в отдельный заголовочный файл. Туда же следует помещать системозависимые определения типов данных (для этой цели стоит использовать typedef). Стандартные системные определения
39
типов содержатся в <sys/types.h>.
•Для локализации системозависимых программных фрагментов следует использовать директивы условной компиляции и директиву #define. Для локализации системозависимых определений типов данных следует использовать typedef.
•Следует особенно тщательно проверять число и тип аргументов, передаваемых функциям (методам классов). Для мобильного определения функций с переменным числом аргументом используйте средства, описанные в файле <varargs.h>. Тип передаваемых в функцию (метод) аргументов должен соответствовать типу формальных параметров; добиться этого необходимо при помощи приведения типов.
•Стоит активно пользоваться модификатором const применительно к пе-
редаваемым в функцию (метод) параметрам для указания того, что эти параметры не могут быть изменены. Например, описание
void func (const SomeClass &);
запрещает модификацию переданного по ссылке объекта функцией func. В идеале все ссылочные аргументы функций (методов) должны быть описаны как const.
•Следует тщательно следить за инициализацией указателей и переполнением их значений. Нельзя полагаться на инициализацию переменных (в т.ч. указателей) по умолчанию.
•Имеет смысл пользоваться классом String для работы со строками. Использование char* в качестве синонима String – это потенциально опасный рудимент «чистого» C.
•Не следует описывать тело метода внутри определения класса. Исключения могут составлять очень короткие методы и короткие перегружаемые операторы. Методы, тело которых включено в определение класса, как правило, реализуются компилятором как inline. При необходимости задать inline-метод (функцию) лучше сделать это путем явного объявления.
•Следует стремиться к тому, чтобы в используемых классах все данные были private. Желательно избегать дружественных (friend) функций и классов.
8.ПОЛЕЗНЫЕ КНИГИ И СТАТЬИ
1.Steve Maguire. Writing Solid Code. //Microsoft Press, 1993.
2.Jocelyn Garner, Senior Technical Writer for MFC. Microsoft Foundation Class Library Development Guidelines. Microsoft Corporation, August 1995.
40
