|
使用這份文件時. 您可以任意的在自己的電腦上保留本文件的備份,這也是我們製作這份文件的目的!如果您發現任何錯誤或是需要調整的地方,請 e-mail 給我 您建議的內容,這樣一來我才能夠將它加入本文件的最新版本中 |
使用“應該”一詞的作用是引導個別專案標準的組成,請依照詞義適當的包含、排除或組合需求。
“也許”一詞相近於“應該”,通常指的是次要的需求。
無論如何,只要最後順利的話,人們將成熟的明白這個標準是合理的,然後其他的程式設計人員也會發現它的合理性,並覺得帶著一些疑問去遵循這個標準是值得的。
如果沒有配合的意願,可以把這個標準改為要求,所有的程式碼都必須基於這個編準做檢驗。
如果沒有經過檢驗,唯一的解決方案會困擾不斷。
命名是程式規劃的核心。古人相信只要知道一個人真正的名字就會獲得淩駕於那個人之上不可思議的力量。
只要你為事物想到正確的名字,將會為你自己以及後來的人帶來比程式碼更強的力量。別笑!
一個名稱是事物在它所處的生態環境中一個長久而深遠的結果,只有了解系統的程式設計人員知道如何建立"適合"這個系統的名稱。如果名稱恰當,許多事情就自然而然結合在一起,關係因此明確,意義因此容易傳達,可以預期一般人心中所推論的結果。
如果您發現自己所有的命名都是東西與命令,您也許需要重新檢視自己的設計。
例如: RetryMax表示最大重試次數, RetryCnt 表示目前重試次數
例如: IsHitRetryLimit.
class FluidOz // 不要用 FluidOZ
class GetHtmlStatistic // 不要用 GetHTMLStatistic
class NameOneTwo
class Name
class JjLinkList
{
}
class NameOneTwo
{
function DoIt() {};
function HandleError() {};
}
class NameOneTwo
{
function VarAbc() {};
function ErrorNumber() {};
var $mVarAbc;
var $mErrorNumber;
var $mrName;
}
class NameOneTwo
{
function StartYourEngines(&$someEngine, &$anotherEngine) {
$this->mSomeEngine = $someEngine;
$this->mAnotherEngine = $anotherEngine;
}
var $mSomeEngine;
var $mAnotherEngine;
}
function HandleError($errorNumber)
{
$error = new OsError;
$time_of_error = $error->GetTimeOfError();
$error_processor = $error->GetErrorProcessor();
}
陣列元件名稱依循變數的規則
$myarr['foo_bar'] = 'Hello';
print "$myarr[foo_bar] world"; // 將會輸出: Hello world
$myarr['foo-bar'] = 'Hello';
print "$myarr[foo-bar] world"; // 警告訊息
$myarr['foo_bar'] = 'Hello';
$element_name = 'foo_bar';
print "$myarr[foo_bar] world"; // 將會輸出: Hello world
print "$myarr[$element_name] world"; // 將會輸出: Hello world
print "$myarr['$element_name'] world"; // 語法錯誤
print "$myarr["$element_name"] world"; // 語法錯誤
class Test
{
var $mrStatus;
function DoSomething(&$rStatus) {};
function &rStatus() {};
}
global $gLog;
global &$grLog;
define("A_GLOBAL_CONSTANT", "Hello world!");
function test()
{
static $msStatus = 0;
}
function some_bloody_function()
{
}
if ($condition) while ($condition)
{ {
... ...
} }
if ($condition) { while ($condition) {
... ...
} }
大家寧可選擇第一種格式的理由比心理學家還多,如果您使用的文字編輯器(例如 VI)支援大括弧的對稱顯示,第一種會比較好。理由是,當您想知道一個大區塊中程式碼結束的位置,您可以移到開始的括弧點一下,編輯器會自動找到結束的括弧。例如:
if ($very_long_condition && $second_very_long_condition)
{
...
}
else if (...)
{
...
}
在區塊間移動您只需要在開始的括弧點一下,您不需要捲動畫面就可以讓游標很快的在區塊間來回。
function func()
{
if (something bad)
{
if (another thing bad)
{
while (more input)
{
}
}
}
}
if (condition)
{
}
while (condition)
{
}
strcmp($s, $s1);
return 1;
例如在完成組態的物件中建立一個方法 Open() 時,Open() 應該在物件完成組態後才去呼叫它
class Device
{
function Device() { /* initialize and other stuff */ }
function Open() { return FAIL; }
};
$dev = new Device;
if (FAIL == $dev->Open()) exit(1);
if (condition) // Comment
{
}
else if (condition) // Comment
{
}
else // Comment
{
}
如果您的程式碼有else if
,加個 else 的區塊來找到沒有處理過的情況,或許可以在此放個記錄,即使它不需要任何矯正的動作
if ( 6 == $errorNum ) ...
其中一個理由是當您少了一個等號時系統會產生錯誤訊息;第二個理由是當您希望尋找變數中放了什麼數值時,您可以在開始的地方找到,而不是在結束的地方。這需要一點時間適應,但是這真的很有幫助!
switch (...)
{
case 1:
...
// FALL THROUGH
case 2:
{
$v = get_week_number();
...
}
break;
default:
}
Continue 與 break 跟 goto 一樣盡可能不要去用它
使用 continue 有兩個比較主要的問題:
下面這個例子中兩個問題都會發生:
while (TRUE)
{
...
// A lot of code
...
if (/* some condition */) {
continue;
}
...
// A lot of code
...
if ( $i++ > STOP_VALUE) break;
}
注意:要讓程式設計人員無法輕易的找出問題 ,"很多的程式碼"是必要的在上面的例子中,我們可以說:在同一個迴圈中同時使用 continue 與 break 必然會是個災難
(condition) ? funct1() : func2();
or
(condition)
? long statement
: another long statement;
var $mDate var& $mrDate var& $mrName var $mName $mDate = 0; $mrDate = NULL; $mrName = 0; $mName = NULL;
while ($dest++ = $src++)
; // VOID
if (FAIL != f())會比下面這樣好:
if (f())即使 FAIL 也等於數值零,會讓PHP判斷為錯誤( false )。如果其他人決定要將錯誤的回傳值定義為 -1 而不是 0 時,一個明確的測試值會很有幫助。又即使比較的數值一定不會改變,明確的比較運算還是必要的!例如 if (!($bufsize % strlen($str))) 應該改寫為 if (0 == ($bufsize % strlen($str))) 來反應測試值的自然數(不是布林數)。經常發生一個錯誤是使用 strcmp 來測試字串是否相等,結果就永遠不會是預設值。
不為零的測試值經常被當做 if 的預設值,而這樣子其他函數或程式就會受到下面這樣的限制:
不要用1 (TRUE, YES, etc.)來檢查布林數值,將它改為不等於0 (FALSE, NO, etc.)。因為大部分的函數保證會在 false 的情況時傳回 0 ,但是true 的狀況會傳回任何不為零的數值,所以...
if (TRUE == func()) { ...
應該要改寫為
if (FALSE != func()) { ...
while ($a != ($c = getchar()))
{
process the character
}
++ 與 -- 兩個運算子是使用指派描述來完成運算,所以在很多情況下執行函式會有些副作用;使用嵌入式指派描述也許會增加執行效能,但是你必須同時考慮加速與不容易維護兩種結果,例如:
$a = $b + $c; $d = $a + $r;不應該寫成
$d = ($a = $b + $c) + $r;即使後者可以節省一個週期,在長時間的執行中,加速器的成熟會讓兩者的差異會越來越小;不過後者在維護上會較困難
發展一個基礎架構可以讓設計的工作輕鬆許多,不管怎麼樣,在還沒開始之前,下面有幾個技巧可以用來鼓勵重複使用程式碼:
其中一個理由是人們不喜歡製作小的函式庫,總覺得這樣怪怪的;別鬧了,電腦不會在乎你使用了多少函式庫
如果你有可以重複使用的程式碼而且無法放在既有的函式庫,麻煩就建立一個新的吧!如果人們真的去思考重複使用的好處,這個函式庫不會一直這麼小的!
如果你擔心在函式庫異動或新增的時候必須去更新 makefiles ,那就不要在 makefiles 中引入(include)函式庫,試著去引入想法或是服務 。 基礎層級的 makefiles 定義了服務是由一堆函式庫所組成,更高層級的 makefiles 指定它們需要的服務,當一個服務的函式庫有了異動,你只需要去修改較低層級的 makefiles 。
在理想的狀況下,程式設計人員可以從網頁瀏覽或搜尋包裝好的函式庫清單,並且取得他們需要的部份。如果你能建立這樣一個讓程式設計人員自願維護的系統,那就太棒了;甚至如果能夠記錄重複使用的狀況更好。
另外一個方法就是自動從程式碼中產生一個資料庫,這是基於使用通用的物件、函數、函式庫與子系統標頭,這樣子同時可以完成說明頁面與資料庫。
這些標頭使用同樣的規則結構化,這樣一來它們可以被分析與分離。他們不像是一般的標頭,所以花點時間去完成填寫。如果你一次作對,你就不需要另外製作文件了!
// :TODO: 年月日 960810: 執行效率問題 // 其實我們應該在這裡使用雜湊表,但是現在先使用線性搜尋。 // :KLUDGE: 年月日 960810: 不安全的強制轉型 // 這裡我們需要作一個強制轉型,以回復原本的衍生型別。或許我們應該 // 使用一個 virtual method、或是使用 template。
在實際上,開放/封閉原則就是要我們好好利用抽象化﹝abstraction﹞與多型 ﹝polymorphism﹞這兩個老朋友。使用抽象化來釐出通用的過程與想法。 使用繼承來建立出衍生類別必須遵守的程式介面。
if ($abool= $bbool) { ... }
這位程式設計師真的是要寫一個指定式嗎?很可能是,但是通常不是。解決這種問題的方法,
就是不要這樣寫。將指定式與邏輯比較式分開來寫,不要將兩者混在一起。建議的格式是, 先執行指定式,再執行邏輯比較:
$abool= $bbool;
if ($abool) { ... }
function example()
{
一堆程式碼
if (0) {
許多程式碼
}
更多程式碼
}
您不能使用 /**/ 的方式來註解一大塊程式碼,因為註解裡面不能再包含 註解,而且很肯定的,一大塊程式碼裡面一定會包含有註解,不是嗎?
class X
{
function GetAge() { return $this->mAge; }
function SetAge($age) { $this->mAge = $age; }
var $mAge;
};
個人並不欣賞Get/Set風格。它會讓程式碼裡面充斥了一堆 Get 與 Set。
但是它有一個好處,在處理網路訊息時,我們可以在 Set 函式裡面將本機的 位元組順序﹝byte order﹞轉換成網路的位元組順序。
class X
{
function Age() { return $this->mAge; }
function Name() { return $this->mName; }
var $mAge;
var $mName;
}
$x = new X;
// 範例 1
$age = $x->Age();
$r_age = &$x->Age(); // 屬性的參考﹝Reference﹞
// 範例 2
$name = $x->Name();
$r_name = &$x->Name(); // 屬性的參考﹝Reference﹞
從函式名稱來看,屬性物件的函式名稱很簡潔。請儘可能使用這種方式來存取屬性。
「階層違背」的發生,就表示有兩個階層之間的互依關係,不是經由良好定義的介面來產生的。 當其中一個階層有所改變時,程式碼可能會無法運作。我們不希望程式碼無法正常運作,所以 要求每個階層只能與鄰近階層進行溝通。
有時候,為了改善執行效率,我們需要跳過幾個階層。這沒有問題,但是我們 應該要知道自己正在違背階層原則,並應該在文件中加以說明。
天阿,這個人在質疑程式碼檢討的必要性,他一定不是工程師!
也不盡然,我所要質疑的,只是程式碼檢討的會議形式,以及在延期又混亂 的專案中進行程式碼檢討的效果。
首先,程式碼檢討只能在專案後期舉行,它來得太遲了,無法產生任何幫助。 真正需要檢討的,是需求規格與設計規格。這才是您可以獲得更大成效的地方。
將相關人員集合在一個房間。把他們鎖在裡面。完整的審視程式介面設計,以及需求 規格,直到前者的設計令人滿意,後者的需求也都達成。將所有相關人員集中在一個 房間裡面,可以讓這個過程產生良好的效益,因為問題可以被立即回答,觀點可以被 立即探討。通常,這種會議只要舉行兩三次就很夠了。
如果上述的過程圓滿達成,程式碼撰寫工作就不會有什麼問題。如果您在程式碼檢討 中發現問題,而這個問題是某人花了許多時間與精神不停測試所發現的,您最好回去將 程式碼重新寫過一遍。
您仍然會想要做程式碼檢討,那只要在一旁進行就好了。找幾位您信得過的人, 閱讀可能有問題的程式碼,並將建議直接告訴程式設計師。如此,程式設計師與審查者 就可以討論各種觀點,並將它們實行出來。電子郵件與快速犀利的討論是很好的方式。 這種方式符合檢討的目的,也不會一次用掉六個人的時間。
某些要點必須牢記在心:
製作一個網頁、文件、或是其他類似的東西。不要讓新到的程式設計師 到處要求老手透露一下建置的秘訣。
程式設計師通常很不喜歡去追蹤臭蟲,然而一旦使用正確,它會對專案有很大的幫助:
原始程式碼的版本控制應該連結到臭蟲追蹤系統上面來。當專案進行到釋出軟體前的 程式碼凍結階段時,版本控制系統應該只接受帶有合法臭蟲ID的存入動作﹝checkin﹞。 當程式碼的臭蟲被修正完畢後,該臭蟲的ID也應該包含在checkin的注解中。
請面對這樣的事實,如果某段程式碼不屬於您維護,就請放棄自己去修改它的念頭。 程式碼的來龍去脈是很複雜的,您認為相當合理的事情,可能到頭來完全是錯誤的。 如果您需要修改某個地方,只要請負責該程式碼的人去修改就好了。或是在您進行 修改之前,先詢問他們是否可以做這樣那樣的改變。如果他們說好,那就可以去改, 否則就請不要動您的編輯器。
每個規則都有例外的情形。如果在清晨三點鐘,您需要做一個修改,以釋出一個 可以使用的版本,那麼您就必須改下去。如果負責人正在休假,而且該模組沒有 指定代理人,您也必須改下去。當您修改別人所撰寫的程式碼時,請試著使用他們 所採用的程式碼風格。
某些特別敏感的程式碼在修改之後會對其他地方造成影響,負責的程式設計師必須在這些程式碼 中加入註解。如果修改某個地方的程式碼,也必須對另一地方的程式碼進行修改,那就 要在註解中加以說明。如果資料格式的改變會與儲存的資料產生衝突、或是與送至遠端電腦的訊息 不一致,也要在註解中加以說明。如果您試著減少記憶體的使用量,或是想達成某種目標, 也請在註解中說明。不會有人聰明到可以猜出您心中的想法。
最不可原諒的是,把整個系統的程式碼全部改過一遍,以符合您個人的程式風格。 請保持禮貌。如果某人沒有按照標準來撰寫程式碼,就請他們修正,或是 請您的經理來要求他們。
如果模組是由大家一起維護的,就要特別小心。堅持不做劇烈的修改,以免產生難以解決的 版本衝突。在程式檔中加入註解,說明這個程式檔的擴增方法,好讓每個人都遵循這個規則。 試著在共用的程式檔中使用相同的程式結構,免得其他人必須摸索著去尋找所要的 程式碼,找到以後也不知道如何去修改它。每次修改完程式碼,立即將新版程式碼 儲存進版本控制軟體中,以避免版本衝突的發生。
另外一提,追蹤臭蟲也是一種必須指定的模組責任。
// 會列印出 "Hello world" print "Hello world"; ?> // 會列印出 "Hello world" // 會列印出 "Hello world" <% print "Hello world"; %> // 會列印出 "Hello world" =$street?> // 會列印出 $street 這個變數的值
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_PAY == $foo) { infinite_loop(); }
else { happy_days_i_know_why_im_here(); }
這樣不是好多了嗎?
一個物件應該有多少method?正確的答案當然是剛剛好,我們稱之為「歌蒂拉克程度」 ﹝Goldilocks level,程度適中的意思﹞。但是「歌蒂拉克程度」是什麼樣子?答案是, 它不存在。您必須依據實際情形來拿捏method的數量,這就是為什麼我們需要程式設計師 :-)
兩種極端的設計是,輕巧類別﹝thin classes﹞與龐大類別﹝thick classes﹞。輕巧類別 是最低限度的類別,它所擁有的method愈少愈好。一般來說,使用者會繼承輕巧類別來衍生 出他們自己的類別,並在衍生類別中加入所需要的method。
輕巧類別或許看起來很「乾淨」,但是實際上並非如此。輕巧類別沒有多大的用途,它的 主要目的只是為了建立起一個型別。正因為輕巧類別沒什麼實際功用,所以在同一個專案 中的每位程式設計師,都會衍生出他們自己的類別,而且他們在衍生類別中所加入的method, 基本上都是一樣的。這就造成了程式碼重複開發的問題,而違背了當初採用物件的方法來 開發程式的目的─讓同一份程式碼可以重覆使用。解決這個問題的方法,就是把新增的method 往上放到基底類別中。如果往上放的method很多,基底類別就會變成龐大類別。
龐大類別有很多method。您想在它裡面加入多少method都可以。這會產生什麼問題嗎?也許不會。 如果要加入的 method 與該類別有直接的關連,那把它們放在該類別裡面就不會有什麼問題。 真正的問題是,有些人會開始懶惰,把有些應該放到別的類別的method,即使與該類別只有一點點關連, 也一併新增到該類別裡面。在這裡,我們需要再次做出正確的判斷。
龐大類別還有其他的問題。愈龐大的類別,愈不容易了解,程式碼之間的互動更不可預期, 因此也更難除錯。而且,如果有一個您從不使用、也從不關心的method被修改了,您還是得 整個類別重新測試,再重新釋出這個類別。