「遊戲引擎Mojoc」(2)C代碼風格

代碼風格的問題,很微妙,也很有趣。因爲它並不影響代碼的運行和功能,但它連接着人的心靈和信仰。代碼風格目的簡單明確,就是增加代碼的可閱讀性,降低維護成本,減少心智負擔。糾結的地方在於,每個人對“可閱讀性”的理解和喜好不同。專注容易產生喜愛,喜愛容易滋生癖好,代碼寫的越多,越會形成個人風格習慣。而邏輯性,還容易在大腦中創造強迫症的傾向,所以代碼風格會演變成信仰,難以動搖。

因爲代碼風格不喜歡,很可能就會否定,一門編程語言,一個框架,一個庫,一個功能等等。

但其實,代碼風格是大腦虛幻確定感的又一力證。因爲,無論什麼風格,喜歡的還是不喜歡的,被迫寫上一段時間,寫上成千上萬行,不喜歡也喜歡了。大腦會潛意識的爲內心的感受,尋找各種各樣的解釋和理由,而感受是會被環境數據所改寫的,到時候又會有新的解讀和視角。

Mojoc的代碼風格,完全不同於C和C++的常用習慣 ,而是最大限度的減少下劃線和宏的使用,沒有好壞對錯,只是個人喜好。

全局命名

使用駝峯命名規則,BigCamelCased 這是大駝峯, smallCamelCased 這是小駝峯。

變量命名

  • 一般變量使用小駝峯。
int               keyLength;
int               valueTypeSize;
SkeletonBone*     bone;
SkeletonBoneData* boneData;
Drawable*         drawable;
  • const 變量使用小駝峯。(因爲還是可以修改的所以和變量一樣)
static const char*  ids[AudioId_Length];
static const char*  saveDataFileName = "MojocSaveDataFile";
static const int    bezierSize       = (BEZIER_SEGMENTS + 1) * 2 - 1;
static const float  subDivPre        = subDivStep3           / pre5;
  • bool 變量使用“is”前綴,整體小駝峯。
SLboolean isLoopEnabled;
bool      isFound;
bool      isRemoved;
particle->isActive
  • 單例變量使用“A”前綴,整體大駝峯。(表示“一個”,從NDK學來的)
extern struct AComponent   AComponent  [1];
extern struct ADrawable    ADrawable   [1];
extern struct AParticle    AParticle   [1];
extern struct AApplication AApplication[1];
  • 函數參數帶出返回值的變量使用“out”前綴,整體小駝峯。
void (*OnSaveData)(void**   outSaveData,   int*         outLength);
void (*Inverse)   (Matrix4* matrix4,       Matrix4*     outInverse);
void Init         (int      valueTypeSize, ArrayIntMap* outArrayIntMap);
  • 函數指針變量使用大駝峯。(因爲會像函數一樣帶括號調用)
typedef struct
{
    void (*OnPause)  ();
    void (*OnResume) ();
    void (*OnDestroy)();
}
ApplicationCallbacks;


typedef float (*TweenActionValueOnGet)(void* target);
typedef void  (*TweenActionValueOnSet)(void* target, float value);


typedef struct
{
    TweenActionValueOnGet OnGet;
    TweenActionValueOnSet OnSet;
}
TweenActionValueGetSet;

縮寫命名

  • 縮寫單詞要麼全部大寫,要麼全部小寫。
typedef struct
{
}
RGB;


RGB rgb;
RGB myRGB;
RGB rgbDatas[10];
RGB myRGBData;


void SetRGB    (RGB* rgb);
void RGBSet    (RGB* rgb);
void SetRGBData(RGB* rgb);

Goto 標籤命名

  • 標籤使用大駝峯,標籤地址使用一般變量命名。
goto ParseArrayEnd;
goto ParseObjectEnd;
goto UseVBO;
goto UseVAO;
goto *coroutine->step;

枚舉命名

  • 枚舉類型使用大駝峯,枚舉變量大駝峯並用下劃線分割前綴。(前綴避免衝突)
enum
{
    HeroState_Stand,
    HeroState_DieOver,
};


enum
{
    CollisionGroup_HeroBody   = 1,
    CollisionGroup_HeroAttack = 1 << 1,
};


typedef enum
{
    FontTextAlignment_HorizontalLeft,
    FontTextAlignment_HorizontalRight,
    FontTextAlignment_VerticalTop,
    FontTextAlignment_VerticalBottom,
}
FontTextAlignment;


typedef enum
{
    InputTouchType_Up     = 1,
    InputTouchType_Down   = 2,
    InputTouchType_Move   = 3,
    InputTouchType_Cancel = 4,
}
InputTouchType;

函數命名

  • 全局函數包括內聯的,使用大駝峯,並用下劃線分割前綴。(前綴避免衝突)
extern void         Application_Main        ();
static inline void  AApplication_AppendChild(Component* child);
static inline float AGLTool_ToGLWidth       (float      screenWidth);
static inline float AMath_Random            ();
  • 局部函數包括內聯的,使用大駝峯。
static void         LoadingRun    (Coroutine* coroutine);
static inline float GetWorldScaleY(Drawable*  drawable);
  • 函數類型定義,使用大駝峯,必須有前綴。(前綴避免衝突)。
typedef float (*TweenActionValueOnGet)(void*      target);
typedef void  (*TweenActionValueOnSet)(void*      target, float value);
typedef void  (*CoroutineRun)         (Coroutine* coroutine);
  • 函數返回bool表示操作是否成功的,使用“Try”前綴。
void* (*TryPut)   (ArrayIntMap* arrayIntMap, intptr_t key, void* valuePtr);
bool  (*TryRemove)(ArrayIntMap* arrayIntMap, intptr_t key);
  • 函數返回bool表示結果的,使用“Is”,“Test”,“Check”前綴。
bool (*IsContains)         (ArrayIntSet*  arrayIntSet, intptr_t element);
bool (*TestPolygonPoint)   (Array(float)* vertexArr,   float x, float y);
bool ADrawable_CheckVisible(Drawable*     drawable);
  • “Release” 命名代表釋放結構體成員所持有的內存空間。
  • “Create” 前綴表示在堆上分配內存。
ArrayList* (*Create)            (int elementTypeSize);
ArrayList* (*CreateWithSize)    (int elementTypeSize, int size);
ArrayList* (*CreateWithCapacity)(int elementTypeSize, int capacity);
  • “Init” 前綴表示初始化已有的內存空間。
void (*Init)            (int elementTypeSize, ArrayList*               outArrayList);
void (*InitWithSize)    (int elementTypeSize, int size,     ArrayList* outArrayList);
void (*InitWithCapacity)(int elementTypeSize, int capacity, ArrayList* outArrayList);

結構體命名

結構體和別名,包括聯合及別名,使用大駝峯

宏定義命名

  • 頭文件的定義,單詞全大寫下劃線分割,“H”後綴。
#ifndef STYLE_GUIDE_H
#define STYLE_GUIDE_H
  • 無參數宏,單詞大寫下劃線分割。
#define MATH_PI  3.141592653589793
#define MATH_2PI 6.283185307179586
#define MATH_PI2 1.570796326794897
  • 有參數宏,大駝峯下劃線分割前綴。(前綴避免衝突)
// not aligned brackets, because macro rules limit
#define AMath_Min(x, y) 
#define AStruct_GetParent2(memberPtr, structType)
#define ACoroutine_YieldFrames(waitFrames)

其它命名

文件名,資源名,文件夾名,通通使用大駝峯。

代碼格式

  • 使用空格縮進,不使用tab縮進。
  • 縮進使用4個空格。
  • 指針類型的星號,靠近變量類型名的一邊。
int*  p1;
int** p2 = &p1;
void* Function(char* str);
  • 左右花括號“{}”垂直對齊。
{
  // vertical alignment
}
  • 參數括號“()” 如果換行了,就垂直對齊。
AMath_Max
(
    animationData->duration,
    AArray_Get
    (
        deformTimeline->frameArr,
        deformTimeline->frameArr->length - 1,
        float
    )
);

static void ReadAnimationDeform
(
    SkeletonData*          skeletonData,
    JsonObject*            jsonDeform,
    SkeletonAnimationData* animationData,
    ArrayList*             skeletonTimelineArr
)
{
   // ...
}
  • if, while, for, switch, 必須有花括號“{}”,與“()”空一格距離。
  • 操作符兩邊至少空一格。
vertexX + (y - vertexY) / (preY - vertexY) * (vertexData[preIndex] - vertexX)
  • case,break依次縮進。
switch (111)
{
    case 0:
        break;

    case 1:
    {
        break;
    }
}
  • 函數上下空兩行間距。
static int a = 100;


static void Function1()
{
}


static inline void Function2()
{
}


struct A
{
}
  • 不同的內容之間空兩行間距。
#include "AAA.h"
#include "BBB.h"


typedef float (*Function1)(void* target);
typedef float (*Function2)();


struct A
{
}


struct B
{
}


extern struct B B[1];
  • 使用分割線,區分不同的邏輯的內容。
// this is split line
//--------------------


void Function1();


//--------------------


void Function2();


void Function3()
{
    int a;

    //---------------- 

    int b;
}
  • goto的標籤縮進與當前行保持一致。
static void Function()
{ 
     goto Label:

     Label1:
     int a;

     Label2:
     int b;

     Label3:
     int c;
}
  • 條件編譯的縮進與當前行保持一致。
typedef struct
{
     Sprite       sprite[1];
     PhysicsBody* body;
     Enemy*       enemy;
     ArrowHitType hitType;

     #ifdef APP_DEBUG
     Drawable     debugDrawable[1];
     #endif
}
Arrow;

void Function()
{
    int a;

    #ifdef APP_DEBUG
    int b;
    #endif
}


#ifdef APP_DEBUG
Drawable debugDrawable[1];
#endif
  • 所有代碼儘量保持,垂直對齊,參看上面所有的例子。(ID Software的規則)

代碼註釋

  • 函數外部使用塊註釋。
/**
 * Comment struct
 */  
struct A
{   
    /**
     * Comment property
     */ 
    int a;

    /**
     * Comment function
     */
    void (*Function)();
}
  • 函數內體使用行註釋 “//”。
  • 註釋代碼塊或多行代碼,如下格式:
/*
--------------------------------------------
  This is means comment few blocks of code
--------------------------------------------
*/

其它規則

  • 參數宏,只有在inline無法滿足的情況下使用。比如,帶有默認參數的函數別名,宏特有的功能體現,泛型參數,變長參數。

  • 不使用0和1作爲bool值判斷,非bool值使用明確的表達式判斷。const變量不能被修改,不可在頭文件定義const變量,在c文件修改const變量的值。


「習慣就好」

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章