Mars's Blog

程式碼寫作風格標準-CodeStyle

目錄


一、說明

團隊開發時,為提升可讀性、降低閱讀成本,程式碼寫作風格的統一是非常重要的,本文件為聲明尊循的業界標準及訂定合乎本團隊使用的標準。


二、公開標準-PSR

PHP Standards Recommendations

2.1. PSR-1

  • 本篇規範制定了代碼基本元素的相關標準,以確保共享的 PHP 代碼間具有較高程度的技術互通性。
  • 本文件中的 必須,不得,需要,應,不應,應該,不應該,推薦,可能 和 可選 等能願動詞按

總覽

  • 檔案只能使用 <?php 和 <?= 標籤
  • 檔案字元編碼只能用 UTF-8 檔首無 BOM
  • 檔案應該只宣告符號 (class、function、constant)或是造成副作用(side-effects,例如產生輸出、修改 .ini 檔之類)兩者擇一不應該兩個都做
  • Namespace 和 Class 必須遵循”自動載入(PSR-4)”的規範
  • Class 命名必須用首字母大寫駝峰式(StudlCaps)
  • Class 常數必須全部用大寫字母命名,多個單字之間用 _ (下底線)連接
  • Method 命名必須用首字母小寫駝峰式(camelCase)

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace app\libraries;

class ExampleLibrary extends NueipLibrary
{
const CONSTANT_VARIABLE = 'test';

public function functionExample()
{

}
}

參考資料:PSR-1基礎編碼規範

2.2. PSR-2

  • 本篇規範是 PSR-1 基本代碼規範的繼承與擴展。
  • 本規范希望通過制定一系列規範化 PHP 代碼的規則,以減少在瀏覽不同作者的代碼時,因代碼風格的不同而造成不便。
  • 當多名程序員在多個項目中合作時,就需要一個共同的編碼規範,
    而本文中的風格規范源自於多個不同項目代碼風格的共同特性,
    因此,本規範的價值在於我們都遵循這個編碼風格,而不是在於它本身。
  • 本文件中的 必須,不得,需要,應,不應,應該,不應該,推薦,可能 和 可選 等能願動詞按

規範

  • 程式碼必須遵循 PSR-1
  • 程式碼必須用 4 個空格做縮排,而不是 tab
  • 一定不能硬性規定一行字元長度,軟性限制必須在 120 個字以內,一行應該在 80 個字元以內
  • 在宣告 namespace 和 use 的區塊後方一定要空一行
  • class 的開始左大括弧必須要換下一行,結束右大括弧必須要換到程式碼下一行
  • method 開始左大括弧必須要換下一行,結束右大括弧必須要換到程式碼下一行
  • 所有的 property 和 method 都必須要宣告可視範圍,abstract 和 final 必須要宣告在可視範圍前,static 必須宣告在可視範圍之後
  • 控制結構關鍵字後面必須要有一個空格,呼叫 method 或 function 時一定不要有空格
  • 控制結構的開始左大括弧必須要在同一行,結束右大括弧必須要換到程式碼下一行
  • 控制結構的開始左小括號後面絕對不要有空格,結束右小括號前面絕對不要有空格

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace app\libraries;

use app\helpers\FileHelper;

class ExampleLibrary extends NueipLibrary
{
const CONSTANT_VARIABLE = 'test';

protected $property;

public function functionExample($score)
{
if ($score >= 90) {
self::visibilityExample($arg1, $arg2);
} elseif ($score >= 60) {
// do something
} else {
// do something
}
}

final public static function visibilityExample()
{
// body
}
}

參考資料:PSR-2 編碼風格規範

2.3. PSR-4

  • PSR-4 描述了從文件路徑中 自動加載 類的規範。它擁有非常好的兼容性,並且可以在任何自動加載規範中使用。 PSR-4 規範也描述了放置 autoload 文件(就是我們經常引入的 vendor/autoload.php)的位置。

規範

  • PSR 明確描述 classes 如何由檔案路徑載入
  • 完全符合規則的命名空間要符合以下格式(class 代表 classes, interfaces, traits)
    1
    \<命名空間>(\<子命名空間>)*\<類別名稱>
    • 完整的 class 名稱必須有最高層級的 namespace,像是大家熟知的 “vendor”
    • 完整的 class 名稱可以有一或多個子命名空間
    • 完整的 class 名稱最後必須要有一個 class
    • 完整的 class 名稱內底線不具有任何特殊含意
    • 完整的 class 名稱內大小寫字母可以任意組合
    • 所有的 class 名稱必須大小寫敏感
  • 使用符合規範的 class 名稱載入檔案時…
    • 在完整的 class 名稱裡不包含最前面的分割符號,後續作為命名空間前綴的一個或多個命名空間和子命名空間必須對應至少一個基本目錄
    • 在命名空間前綴之後的子命名空間對應基本目錄內的子目錄,每一個命名空間分隔符號代表目錄分隔符號,子目錄名稱必須完全符合子命名空間大小寫
    • 最後一個的 class 名稱必須符合檔案名稱且大小寫要相符
  • 自動載入絕對不能拋出錯誤,絕對不能提升至任何錯誤層級,並且不應該有回傳值

使用範例

1
\app\helpers\FileHelper::filterFilename($filename);

參考資料:PSR-4 自動加載規範

2.4. PHPDoc

  • 文檔塊(DocBlock)
    • 短介紹
      • 開始於第一行,以一個空行或一個句號(這裡是指英文句號,下同)結束。單詞中的句號(例如 example.com 或 0.1%)會被忽略。如果短介紹長度超過三行,則只有第一行有效。
    • 長描述
      • 則以任意多行繼續,而且可以包含 用于格式化顯示的 HTML 標記。
      • 下面是 phpDocumentor 所支持的標籤節錄:
        • <br> hard line break, may be ignored by some converters
        • <li> list item
        • <ol> ordered list
        • <p> If used to enclose all paragraphs, otherwise it will be considered text
        • <pre> Preserve line breaks and spacing, and assume all tags are text (like XML’s CDATA)
        • <ul> unordered list
    • 標籤(Tags)
  • 標籤(Tag)
標籤 說明
@abstract 記錄抽像類,類變量或方法。
@access 變數可存取的權限 (Example: Public or Private)
@api 為第三方來源的變數
@author 函數建立者名稱 (Example: @author Mars author@gmail.com)
@category 函數的分類別名,可能某些工具會利用這個來分類你的方法,使好幾個方法歸為某一類,方便做辨識使用
@copyright 函數的版權宣告 (Example: @copyright 隨手寫有限公司 github.com)
@deprecated 代表不建議使用的函數,未來可能會移除這個方法使用到的某個變數,或整個方法都被刪除
@example 代表這個函數使用方式可以參考某個資料,可以使用檔案位置或網址 (Example: @example https://github.com/)
@exception 記錄方法拋出的異常 - 另見@throws
@filesource 這個函數所需的來源
@global 函數內有使用的全域變數註解 (Example: @global Number $user_id)
@ignore 代表這個函數或區域可以被忽略,通常會加上說明
@internal 代表這個函數或區域可能只給予內部使用
@license 此函數可能是含有某個版權或許可 (Example: @license http://opensource.org/licenses/gpl-license.php GNU Public License)
@link 可能與某個網站有關係 (Example: @link https://github.com/)
@method 函數有使用的方法 (Example: @method Array @this->getCategories() or @method String getUserName())
@name 指定變量的別名。 例如,$ GLOBALS [‘myvariable’]變為$ myvariable
@package 利用這個註解來達到細部分層結構 (Example: @package PSR\Documentation\API or @package PSR\Documentation\Doc)
@param 函數要帶入的參數 (Example: @param String,Number $username)
@property 如果這是一個類別的函數,在類別建構時通常會指定初始化參數,而這個函數可能會使用到某些初始化後的參數,稱之為屬性 (Example: @property Resource,Boolean $mysql_connect)
@return 函數最後的回傳值或形態 (Example: @return Array,Object,Boolean)
@see 函數參照或關聯的方法 (Example: @see Class User or @see GuihubBlogs)
@since 函數內某個使用的變數由哪個版本變動 (Example: @since v1.3376a $user_nickname )
@source 這個比較特別,在函數中可以標示從 m 至 n 行 是做什麼事情 (Example: @source 14 21 Get user data)
@static 靜態變數的註解 (Example: @static String $lang = ‘zh_TW’)
@subpackage 利用這個註解來達到細部分層子結構,通常會同時使用 @package,可以參考上面的@package (Example: @package PSR @subpackage Documentation\API)
@throws 例外處理的註解,有多種例外處理的方式,每種方式都不同 (Example: @throws InvalidArgumentException if the provided argument is not of type ‘array’ @throws Exception other…)
@todo 計劃要進行的項目描述,一般應該會使用文字描述
@uses 代表某個元素可能與其它結構有利用關係 (Example: @uses MyClass::$items to retrieve the count from)
@var 變數(物件成員變數)的形態或描述 (Example: @var Boolean)
@version 函數的版本 (Example: v1.3258c)

參考資料:維基-PHPDoc


三、團隊標準

3.1. 函式內部註解

對函式內部重要的邏輯、規則、做法、未做的事註解,以達到說明、記憶、提醒、搜尋定位功能

3.1.1. 功能群組註解

  • 當一組動作共同完成一個任務時,可稱為「功能群組」,對該組動作的任務內容註釋,應放在動作最前方

  • 功能群組註解格式 1:

    1
    // === 功能群組註解 ===

    雙斜線 + 空格 + 三個=號 + 空格 + 註解文字 + 空格 + 三個=號

  • 功能群組註解格式 2:

    1
    /* === 功能群組註解 === */

    單斜線 + *號 + 空格 + 三個=號 + 空格 + 註解文字 + 空格 + *號 + 單斜線

  • 功能群組註解格式 3:

    1
    2
    3
    4
    /*
    * === 功能群組註解 ===
    * 功能群組註解詳細說明
    */

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 取得使用者資料
*
* @param array|string $userList 使用者pk清單
* @param string $sDate 開始日期
* @param string $eDate 結束日期
* @param string $format 格式,如為default時,抓取物件預設格式
*/
public function getUserData($userList, $sDate, $eDate, $format = 'default')
{
// === 參數整理 ===
$userList = is_array($userList) ? $userList : array_map('trim', explode(',', $userList));
$format = $format == 'default' ? self::$format : $format;

// === 資料庫載入 ===
$UserInfo = UserInfo::singleton();
$DeptInfo = DeptInfo::singleton();
$TitleInfo = TitleInfo::singleton();
$Rule = Rule::singleton();

// === 資料取得 ===
// 取得使用者資料
$userData = $UserInfo->getDataByPK($userList);
ArrayHelper::indexBy($userData, 'id');
// 取得部門資料
$deptData = $DeptInfo->getDataByPK();
ArrayHelper::indexBy($deptData, 'id');
// 取得職稱資料
$titleData = $TitleInfo->getDataByPK();
ArrayHelper::indexBy($titleData, 'id');

// === 規則對映表建構 ===
// 取得規則資料-所屬部門歷程
$deptMap = $Rule->deptMapBuilder($userList, $sDate, $eDate);
// 取得規則資料-職稱歷程
$titleMap = $Rule->titleMapBuilder($userList, $sDate, $eDate);
// 取得規則資料-假日
$holidayMap = $Rule->holidayMapBuilder($userList, $sDate, $eDate);

/*
* === 待處理 ===
* TODO: 20190508 本註解為問題描述,如下方程式有問題暫時無法解決時描述、可能是待優化,可全專案搜尋「待處理」、「TODO」
*/

/*
* === 規則 ===
* RULE: 20190508 本註解為特殊規則描述,如下方程式碼規則特殊,可用此方法記錄
*/
}

3.1.2. 單行註解

  • 適用:
    • 變數定義
    • 函式呼叫
    • 行為解釋
    • 搜尋定位
  • 單行註解格式:
    1
    // 單行註解內容

3.2. 命名規則

3.2.1. 原則

  • 使用PSR-1駝峰式命名法

  • 如果在表示SQL來的變數值,可以直接用SQL欄位名 (SQL命名為小寫底線分隔),如:

    1
    2
    // 取得PK為$u_sn使用者的欄位user_id值
    $user_id = $SQL->getUser($u_sn, 'user_id');

    看到此種格式變數即得知與SQL欄位相關,可增加辨識度。

  • 單獨的「名稱」沒有意義,而是要配上「來源/意義」、「說明」,如:

    1
    2
    // 取得員工資料
    $userData = $this->Users_info_model->getUserList();

    變數的「名稱」、「來源/意義」、「說明」可稱為記憶的鐵三角,彼此可互相聯想

  • 在有足夠的辨識度下,名稱愈短愈好

    • 名稱愈短,閱讀、辨識、記憶 愈容易
    • 名稱要的是辨識度,不需完全敘述出作用,敘述是註釋的工作

3.2.2. 辨識度探討

  • 閱讀習慣:以字分段,一句一句來。比較下列辨識度優劣:

    • Rule_detail_value_model
    • RuleDetailValueModel
    • 規則資料
    • 規則詳細資料
    • 規則詳細資料值
      • 英文辨識度:底線分隔 優於 駝峰命名
      • 中文辨識度:因為是母語關系,在一眼可看到整行字時,三者辨識度差不多
  • 名稱長度與辨識度:

    • 優:10字母/2單字 以下
      • 太短的名子,不建議再縮寫,如$ruleData,不宜再縮寫為$rData
      • 如果函式處理流很簡單(行數少),取超短名也不會有誤解/遺忘的狀況
    • 優:10 ~ 20字母/2 ~ 4單字
      • 名稱兼顧易讀性與釋意
    • 劣:20字母/4單字 以上

    名稱定義:

    • 長名稱:20字母/4單字 以上
    • 短名稱:20字母/4單字 之下
    • 完整名稱:很容易從變數命名中看出意義,但有可能很長
      • 例:$userExperienceInformation
    • 縮寫名稱:需輔以命名習慣、註釋理解意義,但短
      • 例:帳號經歷資訊 $userExpInfo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // === 長名稱 ===
    // 取得規則檔頭 - 佳 (短名稱)
    $ruleData = $this->Rule_model->read();
    // 取得規則資料 - 可 (短名稱)
    $ruleDetailData = $this->Rule_detail_model->read();
    // 取得子規則資料 - 劣 (長名稱)
    $ruleDetailValueData = $this->Rule_detail_value_model->read();

    /*
    * === 短名稱-縮寫名稱 ===
    * 依賴習慣、約定命名方式縮寫,但對習慣跳者看Code的人要回頭看註釋/定義
    */
    // 取得規則檔頭 - 可 (短名稱)
    $rData = $this->Rule_model->read();
    // 取得規則資料 - 可 (短名稱)
    $rdData = $this->Rule_detail_model->read();
    // 取得子規則資料 - 可 (短名稱)
    $rdvData = $this->Rule_detail_value_model->read();


    // === 長名稱 vs 短名稱 ===
    // 取得子規則資料 - function內容少時$rdvData較優
    $ruleDetailValueData = $this->Rule_detail_value_model->read();
    $rdvData = $this->Rule_detail_value_model->read();

    // 打卡規則處理 - 長名稱
    foreach ($ruleDetailValueData as $key => $ruleDetailValue) {
    // 問題4
    $ruleDetailValueQuest4 = ruleDetailValue[4];
    // 問題5
    $ruleDetailValueQuest5 = ruleDetailValue[5];

    // 使用者打卡資料
    $userPunch = $userInfo['punch'][$ruleDetailValueQuest4];
    }

    // 打卡規則處理 - 短名稱 - 依賴習慣、約定命名方式
    foreach ($rdvData as $k => $rdv) {
    // 問題4
    $rdvQ4 = $rdv[4];
    // 問題5
    $rdvQ5 = $rdv[5];

    // 使用者打卡資料
    $userPunch = $userInfo['punch'][$rdvQ4];
    }

3.2.3. 命名流派比較

A. 短名稱+說明+來源/意義

例:

1
2
3
// 取得子規則資料  ⇦ 說明
$rdvData = $this->Rule_detail_value_model->read();
⇧ 名稱 ⇧ 來源/意義

特點分析:

  • 名稱:等號左邊的內容,用來辨識為主,理解為輔
  • 說明:註釋,對名稱的敘述,補完對名稱的理解
  • 來源/意義:等號右邊的內容,變數真正的意義由來
  • 有三個記憶/聯想元素
  • 程式處理過程中,短名稱易閱讀、易辨識、易記憶
    • 較佳做法
    • 有時名稱不縮寫就很短了,則不縮寫

B. 長名稱+來源/意義

例:

1
2
$ruleDetailValueData = $this->Rule_detail_value_model->read();
⇧ 名稱 ⇧ 來源/意義

特點分析:

  • 名稱:等號左邊的內容,用名稱盡量描述完變數的意義後,不寫註釋
  • 來源/意義:等號右邊的內容,變數真正的意義由來
  • 有二個記憶/聯想元素
  • 程式處理過程中,長名稱不易閱讀、不易辨識、不易記憶
  • 如看Code喜歡跳著看,或喜歡寫出長長的Code讓變數活很久的人,較喜歡長名稱
    • 較差做法

3.2.4. 常見的命名名詞

  • 動詞:常放在字頭
    • get, set, read, add(create), edit(update), del(delete), remove(rm), find, do, re(重做), allow, deny
  • 判斷詞:常放在字頭
    • is, no(not), had(has), need
  • 性質:常放在字尾
    • data, list, info, map(table), rule, setting(profile), flag, config, type, old, new
    • builder, parser, provider, converter, reader, writer, refactor, reform, reformat, render
  • 複數:字尾
    $salarySnList, $salarySNs
    $userList, $users
  • 常用縮寫:
    • dept(department), sn(series number), id(identification), info(information), del(delete)

四、檔案結構

  • 定義目錄結構及其意義,以利團隊作業
  • 功能需給予分類命名,以提供相似功能下的子文件統一存放目錄

CodeIgniter

  • WebRoot
    • application
      • controllers
        • {功能群組名稱}
          • {功能Controller.php}

            將相關功能劃分為同群組,依群組分類Controller
            CodeIgniter的Controller路徑、檔名會作用於網址,不要用camelcase

      • helpers
        • {Helper函式庫名稱.php}

          放在本目錄的函式庫,皆使用PSR-4方式載入

      • interfaces
        • {功能群組名稱}
          • {功能interface.php}

            放在本目錄的函式庫,皆使用PSR-4方式載入

      • language(語係)
      • libraries
        • {功能群組名稱}
          • {功能Library.php}

            放在本目錄的函式庫,皆使用CI->load->library()方式載入

      • models
        • {資料表Model.php}

          放在本目錄的函式庫,皆使用CI->load->model()方式載入

      • presenters

        放在本目錄的函式庫,皆使用PSR-4方式載入

      • services
        • {函式庫功能}
          • {函式庫.php}

            放在本目錄的函式庫,皆使用PSR-4方式載入

      • vendor (Composer目錄)
      • views
        • {功能群組名稱}
          • {頁面View.php}

            放在本目錄的函式庫,皆使用CI->load->view(), 自定義涵式載入

      • composer.json (Composer安裝檔)
    • assets
      • css
        • inner
          • {功能群組名稱}
            • {頁面CSS.css}

              放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

        • outer
      • js
        • inner
          • {功能群組名稱}
            • {頁面JS.js}

              放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

        • outer
      • img
        • {功能群組名稱}

          放在本目錄的函式庫,皆使用 手動方式、自定義涵式 載入

    • index.php (網站入口)