Petroglif

Code style

В этой главе определены правила оформления исходных кодов в проекте Papyrus.

Предопределенные комментарии

Для того, чтобы унифицировать формальные комментарии, мы сформулировали список наиболее употребительных и разместили его в заголовке файла pp.ini.

Общие правила

Заголовки файлов исходных кодов

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

  • Первая строка - наименование файла прописными буквами без пути. Например SOMEMODULE.CPP.
  • Вторая строка - информация об авторстве и годы модификаций. Чаще всего это что-то вроде: Текст “Copyright (c)”, который встречается во многих файлах проекта - дань традиции. Можно написать так, а можно, например, Author. Но авторство важно.
  • Третья строка - определение кодировки файла с префиксом @codepage. Общее правило - файлы исходных кодов должны быть в кодировке UTF-8. Варианты возможны, но очень не желательны.
  • Четвертая строка - очень краткое (буквально в несколько слов) описание того, что в файле.
  • Пример:
    
    // C_ATURN.CPP
    // Copyright (c) A.Sobolev 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2005, 2006, 2007, 2016, 2019
    // @codepage UTF-8
    // Процедуры корректировки бухгалтерских проводок
    //
    

    Пожалуйста, не украшайте заголовок всякими безвкусными звездочками, длинными линиями из дефисов, знаков '=' и прочей ерунды - помните что чем файл короче тем проще с ним работать. И не надо вносить в исходные коды тексты лицензий и подробные описания ваших взглядов на жизнь.

    Макросы, предотвращающие повторное включение заголовочных файлов (include guard)

    Когда проект запускался директивы guard с помощью макросов (oneof2(a, 1, 3)) аналогично
    	
           if(a == 1 || a == 3 || a == 8) 
    
    следует писать
    		
    	if(oneof3(a, 1, 3))
    

    В slib.h определены макросы от oneof2 до oneof14

    SETIFZ и SETIFZQ

    Вместо

    
           if(!a) 
              a = b; 
    
    следует писать:
    	
           SETIFZ(a, b);
    
    Макрос SETIFZ возвращает true если !!a or !!b. В случае, если нужно по-проще, то применяйте SETIFZQ. Если код компилируется в conformance-mode, то применить SETIFZQ вместо SETIFZ вас вынудит компилятор.

    NZOR

    Вместо
    
           a = b ? b : c; 
    
    следует писать:
    	
           a = NZOR(b, c);
    

    SETMAX

    Вместо

    
           if(a < b)
              a = b;
    
    следует писать:
    	
           SETMAX(a, b); 
    

    SETMIN

    Вместо

    
           if(a > b)
              a = b;
    
    следует писать:
    	
           SETMIN(a, b); 
    

    SETFLAG установка/снятие битовых флагов

    Вместо

    
           if(a) 
              flags |= f;
           else
              flags &=  f;
    
    следует писать:
    	
           SETFLAG(flags, f, a);
    

    ASSIGN_PTR присваивание значения по указателю, если указатель не нулевой

    Вместо

    
           if(p)
              *p = a;
    
    следует писать:
    	
           ASSIGN_PTR(p, a);
    

    RVALUEPTR присваивает значению величину, на которую ссылается указатель, если последний не равен нулю

    Вместо

    
           if(p)
              a = *p;
    
    следует писать:
    	
           RVALUEPTR(a, p);
    
    Кроме того, макрос RVALUEPTR возвращает !0 если его второй аргумент (указатель) не равен нулю и ноль в противном случае. Этой особенностью можно пользоваться для альтернативной инициализации первого аргумента.

    Например:

    
          if(!RVALUEPTR(a, p)) 
              a = 0;
    

    MEMSZERO обнуляет память, занятую переменной

    Подробно описан ниже. См. стр. link.

    THISZERO обнуляет память, занятую переменной по указателю this

    Подробно описан ниже. См. стр. link.

    INITWINAPISTRUCT специальная инициализация структур Win API

    Макрос обнуляет память, выделенную под структуру Win API имеющую член cbSize и присваивает этому члену размер структуры.Например, конструкция:
    
    	SCROLLINFO si;
    	INITWINAPISTRUCT(si);
    

    То же самое, что и:

    
    	SCROLLINFO si;
    	MEMSZERO(si);
    	si.cbSize = sizeof(si);
    

    Задача этого макроса - максимально унифицировать код и сократить его размер.

    Простые преобразования типов указателей

    Следующие макросы осуществляют преобразования указателей к простым типам:

    PTR8(p)
    Преобразует указатель p к типу (uint8 *)
    PTR16(p)
    Преобразует указатель p к типу (uint16 *)
    PTR32(p)
    Преобразует указатель p к типу (uint32 *)
    PTR64(p)
    Преобразует указатель p к типу (uint64 *)
    PTRDBL(p)
    Преобразует указатель p к типу (double *)
    PTR8C(p)
    Преобразует указатель p к типу (const uint8 *)
    PTR16C(p)
    Преобразует указатель p к типу (const uint16 *)
    PTR32C(p)
    Преобразует указатель p к типу (const uint32 *)
    PTR64C(p)
    Преобразует указатель p к типу (const uint64 *)
    PTRDBLC(p)
    Преобразует указатель p к типу (const double *)
    PTRCHR(p)
    Преобразует указатель p к типу (char *) используя static_cast<>
    PTRCHRC(p)
    Преобразует указатель p к типу (const char *) используя static_cast<>
    PTRCHR_(p)
    Преобразует указатель p к типу (char *) используя reinterpret_cast<>
    PTRCHRC_(p)
    Преобразует указатель p к типу (const char *) используя reinterpret_cast<>

    Универсальные семантические правила

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

    Member function IsValid()

    Функция IsValid() проверяет корректность состояния экземпляра объекта.Спецификация функции:
    
    	int IsValid() const;
    

    Квалификатор const может быть опущен лишь в исключительных случаях и с обязательным объяснением.В общем случае, если метод IsValid() экземпляра объекта возвращает нулевое значение, то использовать такой экземпляр нельзя.

    Важно: метод IsValid() не должен быть предназначен только для проверки корректности кода объекта или консистентности внутреннего состояния экземпляра (хотя, кроме прочего, может и это делать). Это - не отладочная семантика. Метод IsValid() должен быть предназначен для регулярной продуктивной проверки состояния экземпляра объекта с целью выяснить можно ли его использовать.Примеры классов, содержащих такой метод: SFile, SBuffer (пример non-const метода), GoodsToObjAssoc.

    Member function IsConsistent()

    Функция IsConsistent() проверяет экземпляр объекта на предмет возможности хоть как-то его использовать. Другими словами, если эта функция возвращает 0, то никакие операции в экземпляром невозможны - он находится в разрушенном состоянии. В отличии от функции IsValid(), отрицательный результат которой лишь говорит о том, что в экземпляре что-то не так, но с ним еще можно что-то сделать (разрушить, инициализировать за-ново), отрицательный результат IsConsistent() вообще исключает возможность дальнейшего взаимодействия с экземпляром.

    Спецификация функции:

    
    	int IsConsistent() const;
    
    Чаще всего, но не всегда, IsConsistent() реализуется посредством проверки предварительно инициализированной сигнатуры экземпляра, значение которой стирается деструктором.

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

    Member function Z()

    Функция Z() инициализирует состояние экземпляра класса или структуры. Эта функция не обязана и, чаще всего, не должна освобождать память, выделенную под внутренние элементы класса если эта память может быть использована при дальнейшей работе с экземпляром.Функция должна вернуть ссылку на экземпляр this.

    Пример:

    
    	class ABC {
    	public:
    		ABC() : A(0), P_Buf(0), UsedBufSize(0), AllocatedBufSize(0)
    		{
    		}
    		 ABC()
    		{
    			// Деструктор, в отличии, от Z() освобождает память,
    			// выделенную под P_Buf, но не трогает "плоские" переменные,
    			// поскольку экземпляр объекта прекращает существование.
    			SAlloc::F(P_Buf);
    		}
    		ABC & Z()
    		{
    			// Мы вернули состояние переменной A в исходное состояние.
    			// Обнулили используемый размер буфера P_Buf, но 
    			// не стали освобождать память, выделенную под 
    			// этот буфер и менять AllocatedBufSize дабы
    			// при последующем использовании не распределять память вновь.
    			A = 0;
    			UsedBufSize = 0;
    			return *this;
    		}
    	private:
    		long   A;
    		void * P_Buf;
    		size_t UsedBufSize;
    		size_t AllocatedBufSize;
    	}
    

    Member function Destroy()

    Функция-член Destroy() должна полностью освободить все ресурсы, занятые экземпляром объекта кроме собственно самого экземпляра this. Вместе с тем, после вызова Destroy() все компоненты экземпляра должны быть валидными в том смысле, что любые дальнейшие операции с экземпляром допускаются. Например, если объект имеет внутренний указатель, распределением которого он управляет, то после освобождения внутреннего содержимого по этому указателю сам указатель должен быть обнулен.

    Спецификация функции:

    
    	void Destroy();
    
    Ключевое отличие Destroy() от Z() в том, что Z() имеет право не освобождать внутренние ресурсы, если считает, что для последующего использования экземпляра они пригодятся. У Destroy() такой опции нет.

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

    
    	class ABC {
    	public:
    		ABC()
    		{
    		}
    		 ABC()
    		{
    			Destroy();
    		}	
    		void Destroy();
    	};
    

    Member function IsEmpty()

    Функция-член IsEmpty возвращает ненулевое значение если экземпляр объекта является “пустым”. Термин “пустой” интерпретируется в зависимости от типа объекта. Спецификация функции, в общем случае, следующая:
    
    int    IsEmpty() const;
    

    Допускаются изменения указанной спецификации, однако, квалификатор const крайне важен. У вас должны быть очень существенные основания для того, чтобы спецификация такого метода была бы non-const.Если класс имеет методы Z() и IsEmpty(), то класс должен выполняться инвариант:

    
    	assert(instance.Z().IsEmpty());
    

    Member function IsEq()

    Функция-член IsEq() сравнивает экземпляр this некоторого объекта с экземпляром, переданным единственным аргументом функции. Чаще всего типом этого аргумента является тот же класс, которому принадлежит функция. Но это - не жесткое правило: могут быть и иные реализации IsEq, принимающие в качестве аргумента какие-либо иные типы.

    Аргумент функции в общем случае следует передавать по const-ссылке.Функция возвращает значение типа int. Если this равен аргументу, то возвращается !0 (любое ненулевое значение: желательно единица, однако допустимы вариации). Если this не равен аргументу, то возвращает 0.

    Функция всегда имеет квалификатор const, равно как и аргумент, передаваемый ей так же имеет квалификатор const. То есть функция не меняет состояние this, равно как и состояние объекта, ссылка на который передана ей для сравнения. Вероятно, исключения из этого правила могут существовать, но в таком случае это необходимо объяснить в комментариях.Общее правило для формата вызова функций этого семейства FASTCALL. Правило не обязательное, но учитывая то, что функция имеет единственный аргумент, применение FASTCALL в этом случае весьма эффективно (большинство компиляторов, обрабатывающих FASTCALL, ничего на стеке передавать не будут - только в регистрах).

    Пример (продолжим примеры с выдуманным классом ABC):

    
    	class ABC {
    	public:
    		ABC() : A(0), P_Buf(0), UsedBufSize(0), AllocatedBufSize(0)
    		{
    		}
    		 ABC()
    		{
    			SAlloc::F(P_Buf);
    		}
    		bool   FASTCALL IsEq(const ABC & rS) const
    		{
    			bool   eq = true;
    			// Попутно проверим очевидные инварианты класса
    			assert(UsedBufSize <= AllocatedBufSize);
    			assert(rS.UsedBufSize <= rS.AllocatedBufSize);
    			assert(!UsedBufSize || P_Buf);
    			assert(!rS.UsedBufSize || rS.P_Buf);
    			if(A != rS.A)			
    				eq = false;
    			else if(UsedBufSize != rS.UsedBufSize)
    				eq = false;
    			else if(UsedBufSize && memcmp(P_Buf, rS.P_Buf, UsedBufSize) != 0) 
    				eq = false;
    			return eq;
    		}
    		ABC & Z()
    		{
    			A = 0;
    			UsedBufSize = 0;
    			return *this;
    		}
    	private:
    		long   A;
    		void * P_Buf;
    		size_t UsedBufSize;
    		size_t AllocatedBufSize;
    	}
    

    Member function Copy()

    Метод Copy() реализует копирование экземпляра объекта, переданного аргументом функции в экземпляр this.

    Спецификация функции, в общем случае, следующая:

    
    	class ABC {
    	public:
    		int    FASTCALL Copy(const ABC & rS);
    	};
    
    Функция Copy() должна быть сконструирована так, чтобы реализовать полное (глубокое) копирование rS в *this. В общем случае, реализация copy-constructor и operator = класса, при необходимости, должны быть имплементированы простым вызовом метода Copy().

    Например:

    
    	class ABC {
    	public:
    		ABC()
    		{
    		}
    		ABC(const ABC & rS)
    		{
    			Copy(rS);
    		}
    		ABC & FASTCALL operator = (const ABC & rS)
    		{
    			Copy(rS);
    			return *this;
    		}
    		int    FASTCALL Copy(const ABC & rS);
    	};
    

    Member function ToStr()

    Метод ToStr реализует преобразование экземпляра объекта в строку. Общие правила (которые не всегда выполняются) следующие:
  • Функция имеет сигнатуру const. Это правило может не выполнятся только в редких исключениях.
  • Строковый буфер, в который осуществляется вывод имеет тип SString & и находится в конце списка аргументов функции.
  • Функция предварительно очищает аргумент буфера вывода методом Z()
  • Функция возвращает либо ссылку на аргумент буфера вывода либо целочисленный индикатор успешности выполнения.
  • OOO "Петроглиф"
    Copyright © 2019