2011年12月6日 星期二

PHP 程式編寫規範


目錄


介紹

標準化的重要性

標準化問題在某些方面上讓每個人頭痛,讓人人都覺得大家處於同樣的境地。這有助於讓這些建
議在許多的項目中不斷演進,許多公司花費了許多星期逐子字逐句的進行爭論。標準化不是特殊
的個人風格,它對本地改良是完全開放的。

優點

當一個項目嘗試著遵守公用的標準時,會有以下好處:
  • 程序員可以瞭解任何代碼,弄清程序的狀況
  • 新人可以很快的適應環境
  • 防止新接觸php的人出於節省時間的需要,自創一套風格並養成終生的習慣
  • 防止新接觸php的人一次次的犯同樣的錯誤
  • 在一致的環境下,人們可以減少犯錯的機會
  • 程序員們有了一致的敵人 :-)

缺點

現在輪到壞處了:
  • 因為標準由一些不懂得php的人所制定,所以標準通常看上去很傻
  • 因為標準跟我做的不一樣,所以標準通常看上去很傻
  • 標準降低了創造力
  • 標準在長期互相合作的人群中是沒有必要的
  • 標準強迫太多的格式
  • 總之人們忽視標準

討論

許多項目的經驗能得出這樣的結論:採用編程標準可以使項目更加順利地完成。標準是成功的關
鍵麼?當然不。但它們可以幫助我們,而且我們需要我們能得到的所有的幫助!老實說,對一個
細節標準的大部分爭論主要是源自自負思想。對一個合理的標準的很少決定能被說為是缺乏技術
性的話,那只是口味的原因罷了。所以,要靈活的控制自負思想,記住,任何項目都取決於團隊
合作的努力。

解釋

慣例

在本文檔中使用「要」字所指的是使用本規範的所有項目需要遵守規定的標準。
使用「應該」一詞的作用是指導項目定制項目細節規範。因為項目必須適當的包括 (include),
排除(exclude)或定製(tailor)需求。
使用「可以」一詞的作用與「應該」類似,因為它指明了可選的需求。

標準實施

首先應該在開發小組的內部找出所有的最重要的元素,也許標準對你的狀況還不夠恰當。它可能已經概
括了 重要的問題,也可能還有人對其中的某些問題表示強烈的反對。

無論在什麼情況下,只要最後順利的話,人們將成熟的明白到這個標準是合理的,然後其他的程序員們
也會發現它的合理性,並覺得帶著一些保留去遵循這一標準是值得的。

如果沒有自願的合作,可以制定需求:標準一定要經過代碼的檢驗。

如果沒有檢驗的話,這個解決方案僅僅是一個建立在不精確的基礎上的一大群可笑的人。

認同觀點

  1. 這行不通;
  2. 也許可行吧,但是它既不實用又無聊;
  3. 這是真的,而且我也告訴過你啊;
  4. 這個是我先想到的;
  5. 本來就應該這樣。
如果您帶著否定的成見而來看待事物的話,請您保持開放的思想。你仍可以做出它是廢話的結論,但是做
出結論的方法就是你必須要能夠接受不同的思想。請您給自己一點時間去做到它。

項目的四個階段

  1. 數據庫結構
  2. 設計
  3. 數據層
  4. HTML層

命名規則

合適的命名

命名是程序規劃的核心。古人相信只要知道一個人真正的名字就會獲得凌駕於那個人之上的不可思議的力
量。只要你給事物想到正確的名字,就會給你以及後來的人帶來比代碼更強的力量。別笑!

名字就是事物在它所處的生態環境中一個長久而深遠的結果。總的來說,只有瞭解系統的程序員才能為系
統取出最合適的名字。如果所有的命名都與其自然相適合,則關係清晰,含義可以推導得出,一般人的推
想也能在意料之中。

如果你發覺你的命名只有少量能和其對應事物相匹配的話, 最好還是重新好好再看看你的設計吧。

類命名

  • 在為類(class )命名前首先要知道它是什麼。如果通過類名的提供的線索,你還是想不起這個類是
    什麼 的話,那麼你的設計就還做的不夠好。
  • 超過三個詞組成的混合名是容易造成系統各個實體間的混淆,再看看你的設計,嘗試使用(CRC Se-
    ssion card)看看該命名所對應的實體是否有著那麼多的功用。
  • 對於派生類的命名應該避免帶其父類名的誘惑,一個類的名字只與它自身有關,和它的父類叫什麼無
    關。
  • 有時後綴名是有用的,例如:如果你的系統使用了代理(agent ),那麼就把某個部件命名為「下
    載代理」(DownloadAgent)用以真正的傳送信息。

方法和函數命名

  • 通常每個方法函數都是執行一個動作的,所以對它們的命名應該清楚的說明它們是做什麼的:用
    CheckForErrors()代替ErrorCheck(),用DumpDataToFile()代替DataFile()。這麼做也可以使功能和
    數據成為更可區分的物體。
  • 有時後綴名是有用的:
    • Max - 含義為某實體所能賦予的最大值。
    • Cnt - 一個運行中的計數變量的當前值。
    • Key - 鍵值。
    例如:RetryMax 表示最多重試次數,RetryCnt 表示當前重試次數。
  • 有時前綴名是有用的:
    • Is - 含義為問一個關於某樣事物的問題。無論何時,當人們看到Is就會知道這是一個問題。
    • Get - 含義為取得一個數值。
    • Set - 含義為設定一個數值
    例如:IsHitRetryLimit。

縮寫詞不要全部使用大寫字母

  • 無論如何,當遇到以下情況,你可以用首字母大寫其餘字母小寫來代替全部使用大寫字母的方法來表
    示縮寫詞。

    使用: GetHtmlStatistic.
    不使用: GetHTMLStatistic.

理由

  • 當命名含有縮略詞時,人們似乎有著非常不同的直覺。統一規定是最好,這樣一來,命名的含義就完
    全可以預知了。
    舉個NetworkABCKey的例子,注意C是應該是ABC裡面的C還是key裡面的C,這個是很令人費解的。有些
    人不在意這些,其他人卻很討厭這樣。所以你會在不同的代碼裡看到不同的規則,使得你不知道怎麼
    去叫它。

例如

   class FluidOz             // 不要寫成 FluidOZ
   class GetHtmlStatistic       // 不要寫成 GetHTMLStatistic


類命名

  • 使用大寫字母作為詞的分隔,其他的字母均使用小寫
  • 名字的首字母使用大寫
  • 不要使用下劃線('_')

理由

  • 根據很多的命名方式,大部分人認為這樣是最好的方式。

例如

   class NameOneTwo
 
   class Name


類庫命名

  • 目前命名空間正在越來越廣泛的被採用,以避免不同廠商和團體類庫間的類名衝突。
  • 當尚未採用命名空間的時候,為了避免類名衝突,一般的做法是在類名前加上獨特的前綴,兩個字符就
    可以了,當然多用一些會更好。

例如

John Johnson的數據結構類庫可以用Jj做為前綴,如下:
   class JjLinkList
   {
   }


方法命名

  • 採用與類命名一致的規則

理由

  • 使用所有不同規則的大部分人發現這是最好的折衷辦法。

例如

   class NameOneTwo
   {
      function DoIt() {};
      function HandleError() {};
   }


類屬性命名

  • 屬性命名應該以字符『m』為前綴。
  • 前綴『m』後採用於類命名一致的規則。
  • 『m』總是在名字的開頭起修飾作用,就像以『r』開頭表示引用一樣。

理由

  • 前綴'm'防止類屬性和方法名發生任何衝突。你的方法名和屬性名經常會很類似,特別是存取元素。

例如

   class NameOneTwo
   {
      function VarAbc() {};
      function ErrorNumber() {};
 
      var mVarAbc;
      var mErrorNumber;
      var mrName;
   }


方法中參數命名

  • 第一個字符使用小寫字母。
  • 在首字符後的所有字都按照類命名規則首字符大寫。

理由

  • 你可以隨時知道那個變量對應那個變量。
  • 你可以使用與類名相似的名稱而不至於產生重名衝突。

例如

   class NameOneTwo
   {
      function StartYourEngines(
                &$rSomeEngine,
                &$rAnotherEngine);
   }


變量命名

  • 所有字母都使用小寫
  • 使用'_'作為每個詞的分界。

理由

  • 通過這一途徑,代碼中變量的作用域是清晰的。
  • 所有的變量在代碼中都看起來不同,容易辨認。

例如

function HandleError($errorNumber)
{
      $error = OsErr();
      $time_of_error = OsErr->getTimeOfError;
      $error_processor = OsErr->getErrorProcessor;
}


引用變量和函數返回引用

  • 引用必須帶『r』前綴

理由

  • 使得類型不同的變量容易辨認
  • 它可以確定哪個方法返回可更改對象,哪個方法返回不可更改對象。

例如

   class Test
   {
      var mrStatus;
 
      function DoSomething(&$rStatus) {};
      function &rStatus() {};
   }


全局變量

  • 全局變量應該帶前綴『g』。

理由

  • 知道一個變量的作用域是非常重要的。

例如

    global $gLog;
    global &$grLog;


定義命名 / 全局常量

  • 全局常量用'_'分隔每個單詞。

理由

這是命名全局常量的傳統。你要注意不要與其它的定義相衝突。

例如

 
define("A_GLOBAL_CONSTANT", "Hello world!");

靜態變量

  • 靜態變量應該帶前綴『s』。

理由

  • 知道一個變量的作用域是非常重要的。

例如

function test()
{
  static $msStatus = 0;
}


函數命名

  • 函數名字採用C GNU的慣例,所有的字母使用小寫字母,使用'_'分割單詞。

理由

  • 這樣可以更易於區分相關聯的類名。

例如

function some_bloody_function()
{
}


錯誤返回檢測規則

  • 檢查所有的系統調用的錯誤信息,除非你要忽略錯誤。
  • 為每條系統錯誤消息定義好系統錯誤文本以便include。


大括號 {} 規則

在三種主要的大括號放置規則中,有兩種是可以接受的,如下的第一種是最好的:
  • 將大括號放置在關鍵詞下方的同列處:
       if ($condition)       while ($condition)
       {                     {
          ...                   ...
       }                     }
  • 傳統的UNIX的括號規則是,首括號與關鍵詞同行,尾括號與關鍵字同列:
       if ($condition) {     while ($condition) {
          ...                   ...
       }                     }

理由

  • 引起劇烈爭論的非原則的問題可通過折衷的辦法解決,兩種方法任意一種都是可以接受的,然而對於大
    多數人來說更喜歡第一種。原因就是心理研究學習範疇的東西了。
    對於更喜歡第一種還有著更多的原因。如果您使用的字符編輯器支持括號匹配功能的話(例如vi),最
    重要的就是有一個好的樣式。為什麼?我們說當你有一大塊的程序而且想知道這一大塊程序是在哪兒結
    束的話。你先移到開始的括號,按下按鈕編輯器就會找到與之對應的結束括號,例如:
         if ($very_long_condition && $second_very_long_condition)
         {
            ...
         }
         else if (...)
         {
       ...
         }
    從一個程序塊移動到另一個程序塊只需要用光標和你的括號匹配鍵就可以了,不需要來回的移動到行末去
    找匹配的括號。


縮進/製表符/空格 規則

  • 使用製表符縮進。
  • 使用三到四個空格為每層次縮進。
  • 不再使用只要一有需要就縮排的方法。對與最大縮進層數,並沒有一個固定的規矩,假如縮進層數大於四或
    者五層的時候,你可以考慮著將代碼因數分解(factoring out code)。

理由

  • 許多編程者支持製表符。
  • Tabs was invented for a rason
  • 當人們使用差異太大的製表符標準的話,會使閱讀代碼變得很費力。
  • 如此多的人願意限定最大的縮進層數,它通常從未被看作是一件工作。我們相信程序員們會明智的選擇嵌套
    的深度。

例如

   function func()
   {
      if (something bad)
      {
         if (another thing bad)
         {
            while (more input)
            {
            }
         }
      }
   }


小括號、關鍵詞和函數 規則

  • 不要把小括號和關鍵詞緊貼在一起,要用空格隔開它們。
  • 不要把小括號和函數名緊貼在一起。
  • 除非必要,不要在Return返回語句中使用小括號。

理由

  • 關鍵字不是函數。如果小括號緊貼著函數名和關鍵字,二者很容易被看成是一體的。

例如

    if (condition)
    {
    }
 
    while (condition)
    {
    }
 
    strcmp($s, $s1);
 
    return 1;


RCS關鍵詞、更改記錄和歷史記錄規則

直接使用RCS關鍵詞的規則必須改變,其中包括使用CVS等類似的支持RCS風格關鍵詞的源代碼控制系統:
  • 別在文件以內使用 RCS 關鍵詞。
  • 別在文件中保存歷史修改記錄。
  • 別在文件中保存作者信息記錄。

理由

  • The reasoning is your source control system already keeps all this information. There is no reason to clutter up source files with duplicate information that:
    • makes the files larger
    • makes doing diffs difficult as non source code lines change
    • makes the entry into the file dozens of lines lower in the file which makes a search or jump necessary for each file
    • is easily available from the source code control system and does not need embedding in the file
  • When files must be sent to other organizations the comments may contain internal details that should not be exposed to outsiders.


別在對象架構期做實際的工作

別在對象架構期做真實的工作,在架構期初始化變量和/或做任何不會有失誤的事情。
當完成對象架構時,為該對象建立一個Open()方法,Open()方法應該以對象實體命名。

理由

  • 構造不能返回錯誤 。

例如

   class Device
   {
      function Device()    { /* initialize and other stuff */ }
      function Open()  { return FAIL; }
   };
 
   $dev = new Device;
   if (FAIL == $dev->Open()) exit(1);




If Then Else 格式

佈局

這由程序員決定。不同的花括號樣式會產生些微不同的樣觀。一個通用方式是:
   if (條件1)                 // 註釋
   {
   }
   else if (條件2)            // 註釋
   {
   }
   else                           // 註釋
   {
   }
如果你有用到else if 語句的話,通常最好有一個else塊以用於處理未處理到的其他情況。可以的話
放一個記錄信息註釋在else處,即使在else沒有任何的動作。

條件格式

總是將恆量放在等號/不等號的左邊,例如:
if ( 6 == $errorNum ) ...
一個原因是假如你在等式中漏了一個等號,語法檢查器會為你報錯。第二個原因是你能立刻找到數值
而不是在你的表達式的末端找到它。需要一點時間來習慣這個格式,但是它確實很有用。



switch 格式

  • Falling through a case statement into the next case statement shall be permitted as long as a comment is included.
  • default case總應該存在,它應該不被到達,然而如果到達了就會觸發一個錯誤。
  • 如果你要創立一個變量,那就把所有的代碼放在塊中。

例如

   switch (...)
   {
      case 1:
         ...
      // FALL THROUGH
 
      case 2:
      {
         $v = get_week_number();
         ...
      }
      break;
 
      default:
   }


continue,break 和 ? 的使用:

Continue 和 Break

Continue 和 break 其實是變相的隱蔽的 goto方法。
Continue 和 break 像 goto 一樣,它們在代碼中是有魔力的,所以要節儉(儘可能少)的使用它們。
使用了這一簡單的魔法,由於一些未公開的原因,讀者將會被定向到只有上帝才知道的地方去。
Continue有兩個主要的問題:
  • 它可以繞過測試條件。
  • 它可以繞過等/不等表達式。
看看下面的例子,考慮一下問題都在哪兒發生:
while (TRUE)
{
   ...
   // A lot of code
   ...
   if (/* some condition */) {
      continue;
   }
   ...
   // A lot of code
   ...
   if ( $i++ > STOP_VALUE) break;
}
注意:"A lot of code"是必須的,這是為了讓程序員們不能那麼容易的找出錯誤。
通過以上的例子,我們可以得出更進一步的規則:continue 和 break 混合使用是引起災難的正確方法。

?:

麻煩在於人民往往試著在 ? 和 : 之間塞滿了許多的代碼。以下的是一些清晰的連接規則:
  • 把條件放在括號內以使它和其他的代碼相分離。
  • 如果可能的話,動作可以用簡單的函數。
  • 把所做的動作,「?」,「:」放在不同的行,除非他們可以清楚的放在同一行。

例如

   (condition) ? funct1() : func2();
 
   or
 
   (condition)
      ? long statement
      : another long statement;


聲明塊的定位

  • 聲明代碼塊需要對齊。

理由Justification

  • 清晰。
  • 變量初始化的類似代碼塊應該列表。
  • The ??token should be adjacent to the type, not the name.

例如

   var       $mDate
   var&      $mrDate
   var&      $mrName
   var       $mName
 
   $mDate    = 0;
   $mrDate   = NULL;
   $mrName   = 0;
   $mName    = NULL;



每行一個語句

除非這些語句有很密切的聯繫,否則每行只寫一個語句。


短方法

  • 方法代碼要限制在一頁內。

理由

  • 這個思想是,每一個方法代表著一個完成單獨目的的技術。
  • 從長遠來說,過多的無效參數是錯誤的。
  • 調用函數比不調用要慢,但是這需要詳細考慮做出決定(見premature optimization 未完善的優化)。


記錄所有的空語句

總是記錄下for或者是while的空塊語句,以便清楚的知道該段代碼是漏掉了,還是故意不寫的。
<tt>
   while ($dest++ = $src++)
      ;         // VOID
</tt>


不要採用缺省方法測試非零值

不要採用缺省值測試非零值,也就是使用:
<tt>
   if (FAIL != f())
</tt>
比下面的方法好:
<tt>
   if (f())
</tt>
即使 FAIL 可以含有 0 值 ,也就是PHP認為false的表示。在某人決定用-1代替0作為失敗返回值的時候,
一個顯式的測試就可以幫助你了。就算是比較值不會變化也應該使用顯式的比較;例如:if (!($bufsize % strlen($str)))
應該寫成:if (($bufsize % strlen($str)) == 0)以表示測試的數值(不是布爾)型。一個經常出
問題的地方就是使用strcmp來測試一個字符等式,結果永遠也不會等於缺省值。
非零測試採用基於缺省值的做法,那麼其他函數或表達式就會受到以下的限制:
  • 只能返回0表示失敗,不能為/有其他的值。
  • 命名以便讓一個真(true)的返回值是絕對顯然的,調用函數IsValid()而不是Checkvalid()。


布爾邏輯類型

大部分函數在FALSE的時候返回0,但是發揮非0值就代表TRUE,因而不要用1(TRUE,YES,諸如此類)等式檢測一個布爾值,應該用0(FALSE,NO,諸如此類)的不等式來代替:
<tt>
   if (TRUE == func()) { ...
</tt>
應該寫成:
<tt>
   if (FALSE != func()) { ...
</tt>


通常避免嵌入式的賦值

有時候在某些地方我們可以看到嵌入式賦值的語句,那些結構不是一個比較好的少冗餘,可讀性強的方法。
<tt>
   while ($a != ($c = getchar()))
   {
      process the character
   }
</tt>
++和--操作符類似於賦值語句。因此,出於許多的目的,在使用函數的時候會產生副作用。使用嵌入式賦值
提高運行時性能是可能的。無論怎樣,程序員在使用嵌入式賦值語句時需要考慮在增長的速度和減少的可維
護性兩者間加以權衡。例如:
<tt>
   a = b + c;
   d = a + r;
</tt>
不要寫成:
<tt>
   d = (a = b + c) + r;
</tt>
雖然後者可以節省一個週期。但在長遠來看,隨著程序的維護費用漸漸增長,程序的編寫者對代碼漸漸遺忘,
就會減少在成熟期的最優化所得。


重用您和其他人的艱苦工作

跨工程的重用在沒有一個通用結構的情況下幾乎是不可能的。對象符合他們現有的服務需求,不同的過程有著
不同的服務需求環境,這使對象重用變得很困難。
開發一個通用結構需要預先花費許多的努力來設計。當努力不成功的時候,無論出於什麼原因,有幾種辦法推
薦使用:

請教!給群組發Email求助

這個簡單的方法很少被使用。因為有些程序員們覺得如果他向其他人求助,會顯得自己水平低,這多傻啊!做新
的有趣的工作,不要一遍又一遍的做別人已經做過的東西。
如果你需要某些事項的源代碼,如果已經有某人做過的話,就向群組發email求助。結果會很驚喜哦!
在許多大的群組中,個人往往不知道其他人在幹什麼。你甚至可以發現某人在找一些東西做,並且自願為你寫代
碼,如果人們在一起工作,外面就總有一個金礦。

告訴!當你在做事的時候,把它告訴所有人

如果你做了什麼可重用的東西的話,讓其他人知道。別害羞,也不要為了保護自豪感而把你的工作成果藏起來。
一旦養成共享工作成果的習慣,每個人都會獲得更多。

Don't be Afraid of Small Libraries

對於代碼重用,一個常見的問題就是人們不從他們做過的代碼中做庫。一個可重用的類可能正隱蔽在一個程序目
錄並且決不會有被分享的激動,因為程序員不會把類分拆出來加入庫中。
這樣的其中一個原因就是人們不喜歡做一個小庫,對小庫有一些不正確感覺。把這樣的感覺克服掉吧,電腦才不
關心你有多少個庫呢。
如果你有一些代碼可以重用,而且不能放入一個已經存在的庫中,那麼就做一個新的庫吧。如果人們真的考慮重
用的話,庫不會在很長的一段時間裡保持那麼小的。
If you are afraid of having to update makefiles when libraries are recomposed or added then don't include libraries in your makefiles, include the idea of services. Base level makefiles define services that are each composed of a set of libraries. Higher level makefiles specify the services they want. When the libraries for a service change only the lower level makefiles will have to change.

Keep a Repository

Most companies have no idea what code they have. And most programmers still don't communicate what they have done or ask for what currently exists. The solution is to keep a repository of what's available.
In an ideal world a programmer could go to a web page, browse or search a list of packaged libraries, taking what they need. If you can set up such a system where programmers voluntarily maintain such a system, great. If you have a librarian in charge of detecting reusability, even better.
Another approach is to automatically generate a repository from the source code. This is done by using common class, method, library, and subsystem headers that can double as man pages and repository entries.


評價註釋

註釋應該是講述一個故事

Consider your comments a story describing the system. Expect your comments to be extracted by a robot and formed into a man page. Class comments are one part of the story, method signature comments are another part of the story, method arguments another part, and method implementation yet another part. All these parts should weave together and inform someone else at another point of time just exactly what you did and why.

Document Decisions

Comments should document decisions. At every point where you had a choice of what to do place a comment describing which choice you made and why. Archeologists will find this the most useful information.

使用標頭說明

利用類似ccdoc的文檔抽取系統。在這一文檔的其他部分描述的是怎麼利用ccdoc記錄一個類和方法。
這些標頭說明可以以這樣的一個方式來提取並分析和加以組織,它們不像一般的標頭一樣是無用的。
因此花時間去填上他吧。

註釋佈局

工程的每部分都有特定的註釋佈局。

Make Gotchas Explicit

Explicitly comment variables changed out of the normal control flow or other code likely to break during maintenance. Embedded keywords are used to point out issues and potential problems. Consider a robot will parse your comments looking for keywords, stripping them out, and making a report so people can make a special effort where needed.

Gotcha Keywords

  • :TODO: topic
    Means there's more to do here, don't forget.
  • :BUG: [bugid] topic
    means there's a Known bug here, explain it and optionally give a bug ID.
  • :KLUDGE:
    When you've done something ugly say so and explain how you would do it differently next time if you had more time.
  • :TRICKY:
    Tells somebody that the following code is very tricky so don't go changing it without thinking.
  • :WARNING:
    Beware of something.
  • :PHARSER:
    Sometimes you need to work around a pharser problem. Document it. The problem may go away eventually.
  • :ATTRIBUTE: value
    The general form of an attribute embedded in a comment. You can make up your own attributes and they'll be extracted.

Gotcha Formatting

  • Make the gotcha keyword the first symbol in the comment.
  • Comments may consist of multiple lines, but the first line should be a self-containing, meaningful summary.
  • The writer's name and the date of the remark should be part of the comment. This information is in the source repository, but it can take a quite a while to find out when and by whom it was added. Often gotchas stick around longer than they should. Embedding date information allows other programmer to make this decision. Embedding who information lets us know who to ask.

Example

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

See Also

See Interface and Implementation Documentation for more details on how documentation should be laid out.


Interface and Implementation Documentation

There are two main audiences for documentation:
  • Class Users
  • Class Implementors
With a little forethought we can extract both types of documentation directly from source code.

Class Users

Class users need class interface information which when structured correctly can be extracted directly from a header file. When filling out the header comment blocks for a class, only include information needed by programmers who use the class. Don't delve into algorithm implementation details unless the details are needed by a user of the class. Consider comments in a header file a man page in waiting.

Class Implementors

Class implementors require in-depth knowledge of how a class is implemented. This comment type is found in the source file(s) implementing a class. Don't worry about interface issues. Header comment blocks in a source file should cover algorithm issues and other design decisions. Comment blocks within a method's implementation should explain even more.


目錄文檔

所有的目錄下都需要具有README文檔,其中包括:
  • 該目錄的功能及其包含內容
  • 一個對每一文件的在線說明(帶有link),每一個說明通常還應該提取文件標頭的一些屬性名字。
  • 包括設置、使用說明
  • 指導人民如何連接相關資源:
    • 源文件索引
    • 在線文檔
    • 紙文檔
    • 設計文檔
  • 其他對讀者有幫助的東西
考慮一下,當每個原有的工程人員走了,在6個月之內來的一個新人,那個孤獨受驚嚇的探險者通過整個
工程的源代碼目錄樹,閱讀說明文件,源文件的標頭說明等等做為地圖,他應該有能力穿越整個工程。


Use a Design Notation and Process

Programmers need to have a common language for talking about coding, designs, and the software process in general. This is critical to project success.
Any project brings together people of widely varying skills, knowledge, and experience. Even if everyone on a project is a genius you will still fail because people will endlessly talk past each other because there is no common language and processes binding the project together. All you'll get is massive fights, burnout, and little progress. If you send your group to training they may not come back seasoned experts but at least your group will all be on the same page; a team.
There are many popular methodologies out there. The point is to do some research, pick a method, train your people on it, and use it. Take a look at the top of this page for links to various methodologies.
You may find the CRC (class responsibility cards) approach to teasing out a design useful. Many others have. It is an informal approach encouraging team cooperation and focusing on objects doing things rather than objects having attributes. There's even a whole book on it: Using CRC Cards by Nancy M. Wilkinson.

Using Use Cases

use case is a generic description of an entire transaction involving several objects. A use case can also describe the behaviour of a set of objects, such as an organization. A use case model thus presents a collection of use cases and is typically used to specify the behavior of a whole application system together with one or more external actors that interact with the system.
An individual use case may have a name (although it is typically not a simple name). Its meaning is often written as an informal text description of the external actors and the sequences of events between objects that make up the transaction. Use cases can include other use cases as part of their behaviour.

Requirements Capture

Use cases attempt to capture the requirements for a system in an understandable form. The idea is by running through a set of use case we can verify that the system is doing what it should be doing.
Have as many use cases as needed to describe what a system needs to accomplish.

The Process

  • Start by understanding the system you are trying to build.
  • Create a set of use cases describing how the system is to be used by all its different audiences.
  • Create a class and object model for the system.
  • Run through all the use cases to make sure your model can handle all the cases. Update your model and create new use cases as necessary.


Open/Closed Principle

The Open/Closed principle states a class must be open and closed where:
  • open means a class has the ability to be extended.
  • closed means a class is closed for modifications other than extension. The idea is once a class has been approved for use having gone through code reviews, unit tests, and other qualifying procedures, you don't want to change the class very much, just extend it.
The Open/Closed principle is a pitch for stability. A system is extended by adding new code not by changing already working code. Programmers often don't feel comfortable changing old code because it works! This principle just gives you an academic sounding justification for your fears :-)
In practice the Open/Closed principle simply means making good use of our old friends abstraction and polymorphism. Abstraction to factor out common processes and ideas. Inheritance to create an interface that must be adhered to by derived classes.


Design by Contract

The idea of design by contract is strongly related to LSP . A contract is a formal statement of what to expect from another party. In this case the contract is between pieces of code. An object and/or method states that it does X and you are supposed to believe it. For example, when you ask an object for its volume that's what you should get. And because volume is a verifiable attribute of a thing you could run a series of checks to verify volume is correct, that is, it satisfies its contract.
The contract is enforced in languages like Eiffel by pre and post condition statements that are actually part of the language. In other languages a bit of faith is needed.
Design by contract when coupled with language based verification mechanisms is a very powerful idea. It makes programming more like assembling spec'd parts.


其他雜項

這一部分包含著各種各樣的該做的和不該做的。

  • 在需要用到離散的數值使,不要使用浮點數變量。採用浮點數來做循環計數器無異於向自己的腳
    開槍。測試浮點數時總要使用 <= 或 => ,永遠不要用 = 或 => 。
  • 不要使用程序自動美化器,得益於好的程序樣式的主要的人就是程序員自己,特別是剛開著手代
    碼、算法設計的程序員,使用程序自動美化器僅僅能根據語法來更正程序,因此當對空白和縮進
    的注意有很大需要時,它是不可能做到的。正常的細心注意細節的程序員們能很好的用清晰直觀
    的樣式來完成一個函數或文件(換句話來說,一些直觀的樣式是意向的規定而不是程序自動美化
    器能讀懂的智慧)。馬虎的程序員應該學習細緻的程序員,不要依賴程序自動美化器來增加程序
    的可讀性。最初的美化器是必須分析源代碼的程序,複雜的美化器不值得通過這樣獲得好處,美
    化器最好用於生成總的機器建立(machine-generated)格式代碼。
  • 對邏輯表達式第二個 = 不小心的忽略是一個問題,以下顯得混亂而且更像是錯誤:
            if ($abool= $bbool) { ... }
     
    程序員在這裡真的是要賦值麼?一般常常是,但通常又不是這樣。這樣避免引起這樣的混亂呢?解
    決方案就是不要這樣做,利用顯式和隱式的判斷測試,推薦的方法是在做測試前先做賦值:
    <tt>
           $abool= $bbool;
           if ($abool) { ... }
        </tt>

使用if (0)來註釋外部代碼塊

有時需要註釋大段的測試代碼,最簡單的方法就是使用if (0)塊:
   function example()
   {
      great looking code
 
      if (0) {
      lots of code
      }
 
      more code
    }
你不能使用/**/,因為註釋內部不能包含註釋,而大段的程序中可以包含註釋,不是麼?

Different Accessor Styles

Why Accessors?

Access methods provide access to the physical or logical attributes of an object. We disallow direct access to attributes to break dependencies, the reason we do most things. Directly accessing an attribute exposes implementation details about the object.
To see why ask yourself:
  • What if the object decided to provide the attribute in a way other than physical containment?
  • What if it had to do a database lookup for the attribute?
  • What if a different object now contained the attribute?
If any of the above changed code would break. An object makes a contract with the user to provide access to a particular attribute; it should not promise how it gets those attributes. Accessing a physical attribute makes such a promise.

Implementing Accessors

There are three major idioms for creating accessors.

Get/Set

   class X
   {
      function GetAge()        { return $this->mAge; }
      function SetAge($age)    { $mAge= $age; }
      var $mAge;
   }

One Method Name

   class X
   {
      function Age()           { return $mAge; }
      function Age($age)       { $mAge= $age; }
      var $mAge;
   }
Similar to Get/Set but cleaner. Use this approach when not using the Attributes as Objects approach.

Attributes as Objects

   class X
   {
      function Age()           { return $mAge; }
      function rAge()          { return &$mAge; }
 
      function Name()          { return mName; }
      function rName()         { return &$mName; }
 
      var $mAge;
      var $mName;
   }
 
   X $x;
   $x->rName()= "test";
The above two attribute examples shows the strength and weakness of the Attributes as Objects approach.
When using rAge(), which is not a real object, the variable is set directly because rAge() returns a reference. The object can do no checking of the value or do any representation reformatting. For many simple attributes, however, these are not horrible restrictions.

Layering

Layering is the primary technique for reducing complexity in a system. A system should be divided into layers. Layers should communicate between adjacent layers using well defined interfaces. When a layer uses a non-adjacent layer then a layering violation has occurred.
A layering violation simply means we have dependency between layers that is not controlled by a well defined interface. When one of the layers changes code could break. We don't want code to break so we want layers to work only with other adjacent layers.
Sometimes we need to jump layers for performance reasons. This is fine, but we should know we are doing it and document appropriately.


Code Reviews

If you can make a formal code review work then my hat is off to you. Code reviews can be very useful. Unfortunately they often degrade into nit picking sessions and endless arguments about silly things. They also tend to take a lot of people's time for a questionable payback.
My god he's questioning code reviews, he's not an engineer!
Not really, it's the form of code reviews and how they fit into normally late chaotic projects is what is being questioned.
First, code reviews are way too late to do much of anything useful. What needs reviewing are requirements and design. This is where you will get more bang for the buck.
Get all relevant people in a room. Lock them in. Go over the class design and requirements until the former is good and the latter is being met. Having all the relevant people in the room makes this process a deep fruitful one as questions can be immediately answered and issues immediately explored. Usually only a couple of such meetings are necessary.
If the above process is done well coding will take care of itself. If you find problems in the code review the best you can usually do is a rewrite after someone has sunk a ton of time and effort into making the code "work."
You will still want to do a code review, just do it offline. Have a couple people you trust read the code in question and simply make comments to the programmer. Then the programmer and reviewers can discuss issues and work them out. Email and quick pointed discussions work well. This approach meets the goals and doesn't take the time of 6 people to do it.


Create a Source Code Control System Early and Not Often

A common build system and source code control system should be put in place as early as possible in a project's lifecycle, preferably before anyone starts coding. Source code control is the structural glue binding a project together. If programmers can't easily use each other's products then you'll never be able to make a good reproducible build and people will piss away a lot of time. It's also hell converting rogue build environments to a standard system. But it seems the right of passage for every project to build their own custom environment that never quite works right.
Some issues to keep in mind:
  • Shared source environments like CVS usually work best in largish projects.
  • If you use CVS use a reference tree approach. With this approach a master build tree is kept of various builds. Programmers checkout source against the build they are working on. They only checkout what they need because the make system uses the build for anything not found locally. Using the -I and -L flags makes this system easy to setup. Search locally for any files and libraries then search in the reference build. This approach saves on disk space and build time.
  • Get a lot of disk space. With disk space as cheap it is there is no reason not to keep plenty of builds around.
  • Make simple things simple. It should be dead simple and well documented on how to:
    • check out modules to build
    • how to change files
    • how to add new modules into the system
    • how to delete modules and files
    • how to check in changes
    • what are the available libraries and include files
    • how to get the build environment including all compilers and other tools
    Make a web page or document or whatever. New programmers shouldn't have to go around begging for build secrets from the old timers.
  • On checkins log comments should be useful. These comments should be collected every night and sent to interested parties.

Sources

If you have the money many projects have found Clear Case a good system. Perfectly workable systems have been build on top of GNU make and CVS. CVS is a freeware build environment built on top of RCS. Its main difference from RCS is that is supports a shared file model to building software.


Create a Bug Tracking System Early and Not Often

The earlier people get used to using a bug tracking system the better. If you are 3/4 through a project and then install a bug tracking system it won't be used. You need to install a bug tracking system early so people will use it.
Programmers generally resist bug tracking, yet when used correctly it can really help a project:
  • Problems aren't dropped on the floor.
  • Problems are automatically routed to responsible individuals.
  • The lifecycle of a problem is tracked so people can argue back and forth with good information.
  • Managers can make the big schedule and staffing decisions based on the number of and types of bugs in the system.
  • Configuration management has a hope of matching patches back to the problems they fix.
  • QA and technical support have a communication medium with developers.
Not sexy things, just good solid project improvements.
FYI, it's not a good idea to reward people by the number of bugs they fix :-)
Source code control should be linked to the bug tracking system. During the part of a project where source is frozen before a release only checkins accompanied by a valid bug ID should be accepted. And when code is changed to fix a bug the bug ID should be included in the checkin comments.

Sources

Several projects have found DDTS a workable system (I 've not verified this link for this PHP release, DDTS may not work for PHP). There is also a GNU bug tracking system available. Roll your own is a popular option but using an existing system seems more cost efficient.


Honor Responsibilities

Responsibility for software modules is scoped. Modules are either the responsibility of a particular person or are common. Honor this division of responsibility. Don't go changing things that aren't your responsibility to change. Only mistakes and hard feelings will result.
Face it, if you don't own a piece of code you can't possibly be in a position to change it. There's too much context. Assumptions seemingly reasonable to you may be totally wrong. If you need a change simply ask the responsible person to change it. Or ask them if it is OK to make such-n-such a change. If they say OK then go ahead, otherwise holster your editor.
Every rule has exceptions. If it's 3 in the morning and you need to make a change to make a deliverable then you have to do it. If someone is on vacation and no one has been assigned their module then you have to do it. If you make changes in other people's code try and use the same style they have adopted.
Programmers need to mark with comments code that is particularly sensitive to change. If code in one area requires changes to code in an another area then say so. If changing data formats will cause conflicts with persistent stores or remote message sending then say so. If you are trying to minimize memory usage or achieve some other end then say so. Not everyone is as brilliant as you.
The worst sin is to flit through the system changing bits of code to match your coding style. If someone isn't coding to the standards then ask them or ask your manager to ask them to code to the standards. Use common courtesy.
Code with common responsibility should be treated with care. Resist making radical changes as the conflicts will be hard to resolve. Put comments in the file on how the file should be extended so everyone will follow the same rules. Try and use a common structure in all common files so people don't have to guess on where to find things and how to make changes. Checkin changes as soon as possible so conflicts don't build up.
As an aside, module responsibilities must also be assigned for bug tracking purposes.

PHP文件擴展名

我見過許多種PHP文件的擴展名(.html, .php, .php3, .php4, .phtml, .inc, .class...)
  • 所有瀏覽者可見頁面使用.html
  • 所有類、函數庫文件使用.php

理由

  • 擴展名描述的是那種數據是用戶將會收到的。PHP是解釋為HTML的。


不要不可思議的數字

一個在源代碼中使用了的赤裸裸的數字是不可思議的數字,因為包括作者,在三個月內,沒人它的含義。例如:
if      (22 == $foo) { start_thermo_nuclear_war(); }
else if (19 == $foo) { refund_lotso_money(); }
else if (16 == $foo) { infinite_loop(); }
else                { cry_cause_im_lost(); }
在上例中22和19的含義是什麼呢?如果一個數字改變了,或者這些數字只是簡單的錯誤,你會怎麼想?
使用不可思議的數字是該程序員是業餘運動員的重要標誌,這樣的程序員從來沒有在團隊環境中工作過,
又或者是為了維持代碼而不得不做的,否則他們永遠不會做這樣的事。
你應該用define()來給你想表示某樣東西的數值一個真正的名字,而不是採用赤裸裸的數字,例如:
define("PRESIDENT_WENT_CRAZY", "22");
define("WE_GOOFED", "19");
define("THEY_DIDNT_PAY", "16");
 
if      (PRESIDENT_WENT_CRAZY == $foo) { start_thermo_nuclear_war(); }
else if (WE_GOOFED            == $foo) { refund_lotso_money(); }
else if (THEY_DIDNT_PA

沒有留言: