不管你現在的編程技能有多麼的高超,曾經你也是個亦步亦趨,不斷的學習的初學者。在編程這條曲折的道路上,我想你肯定犯過一些低級的錯誤、遇見過一些普通的編碼陷阱。本文作者跨越多個語言,爲大家總結了20條常規陷阱,並提供瞭解決方案。
JavaScript篇
1.不必要的DOM操作
例如下面這段代碼:
-
- for (var i = 0; i < 100; i++){
- var li = $("<li>").html("This is list item #" + (i+1));
- $("#someUL").append(li);
- }
這段代碼對DOM進行了100次修改,並且創建了100個不必要的jQuery對象。正確的做法是使用一個文檔片段,或者創建一個字符串,把100個<li>元素賦給該字符串。然後附加到HTML中。這樣就只需運行DOM一次,代碼如下:
- var liststring = "";
- for (var i = 100; i > 0; i--){
- liststring += "<li>This is list item #" + (99- i);
- }
- document.getElementById("someUL").innerHTML(liststring);
正如上面所描述的一樣,下面再提供一個方式,使用數組:
- var liststring = "<li>"
- var lis = [];
- for (var i = 100; i > 0; i--){
- lis.push("This is list item #" + (99- i));
- }
- liststring += lis.join("</li><li>") + "</li>";
- document.getElementById("someUL").innerHTML(liststring);
這是在JavaScript創建重複HTML最快最簡單的方法,無需使用模板庫或框架。
2.不一致的變量名和函數名
這個問題是非常重要的,尤其當你在別人的代碼上工作時,一定要保持標識符(變量名和函數名)一致,例如下面這段代碼:
- var foo = "bar";
- var plant = "green";
- var car = "red";
通常,人們並不會設置變量名叫Something,這涉及到命名規則問題,命名應清晰明瞭,一目瞭然。很多編程語言地變量命名都使用大寫。
下面是對函數的命名:
- function subtractFive(number){
- return number - 5;
- }
語法結構清晰並且能起到解釋性功能。
例如想要對給定的數字加5,仍採用上述命名模式,比如:
- function addFive(number){
- return number + 5;
- }
有時,你會根據返回值命名,例如該函數要返回一個HTML字符串,那麼可以命名爲getTweetHTML(),如果函數只是做一些操作,無需返回值,那麼可以在前面加一個do前綴。例如doFetchTweets()。
構造函數通常會遵循類原則,大寫第一個字母:
- function Dog(color){
- this.color = color;
- }
命名應帶有描述性,比如操作型的函數在前面加do,另外要具備可讀性和提示性。
3.在for...Loops中使用hasOwnProperty()方法
JavaScript數組是沒有關聯的,可以把它當做哈希表,使用循環來遍歷對象屬性:
- for (var prop in someObject) {
- alert(someObject[prop]);
- }
然而,存在的問題是for...in loop是在對象屬性鏈上遍歷每個枚舉類型的屬性,如果你只想使用對象實際擁有的屬性,這可能有問題的。那怎麼解決呢?你可以使用hasOwnProperty()方法。代碼如下:
- for (var prop in someObject) {
- if (someObject.hasOwnProperty(prop)) {
- alert(someObject[prop]);
- }
- }
4.比較布爾值
把布爾值作爲條件進行比較,其實這是在浪費電腦的計算時間。看下面這個例子吧:
- if (foo == true) {
-
- } else {
-
- }
其實foo==true這個比較完全是多餘的,因爲foo已經是布爾類型。直接這樣寫就行:
- if (foo) {
-
- } else {
-
- }
又或者這樣寫:
- if (!foo) {
-
- } else {
-
- }
5.事件綁定
在JavaScript中,事件是個複雜的問題。事件冒泡(event bubbling)和委託正在取代內聯事件(inline onclick)操作(一些特殊的“初始頁”除外)。
假設你有一個圖片網格,需要啓動一個modal lightbox窗口。千萬不要採取下面的做法,示例採用的是jQuery,如果你使用相似的庫或者其他,冒泡機制也同樣適合傳統的JavaScript。
相關的HTML代碼:
- <div id="grid-container">
- <a href="someimage.jpg"><img src="someimage-thumb.jpg"></a>
- <a href="someimage.jpg"><img src="someimage-thumb.jpg"></a>
- <a href="someimage.jpg"><img src="someimage-thumb.jpg"></a>
- ...
- </div>
不好的JavaScript寫法:
- $('a').on('click', function() {
- callLightbox(this);
- });
這段代碼假設調用lightbox,裏面傳遞一個anchor元素並且引用全屏圖片。與其綁定每個anchor元素還不如直接使用#grid-container元素。
- $("#grid-container").on("click", "a", function(event) {
- callLightbox(event.target);
- });
在這段代碼中,this和event.target都表示anchor元素。同樣你也可以在任何父元素上使用。只要保證所定義的元素是事件目標就行(event's target)。
6.避免三元冗餘
在JavaScript和PHP中,過度使用三元語句是很常見的事情:
-
- return foo.toString() !== "" ? true : false;
-
- return (something()) ? true : false;
條件判斷的返回值永遠只有false和true,言外之意就是你無需把true和false顯示添加到三元運算中。相反,你只需簡單的返回條件:
-
- return foo.toString() !== "";
-
- return something();
PHP篇
7.適當的時候使用三元操作
If...else語句是大多數語言的重要組成部分。但有些簡單的事情,比如根據條件進行賦值,你很有可能會這樣寫:
- if ($greeting)
- {
- $post->message = 'Hello';
- }
- else
- {
- $post->message = 'Goodbye';
- }
其實使用三元操作只需一行代碼就可以搞定,並保持了良好的可讀性:
- $post->message = $greeting ? 'Hello' : 'Goodbye';
8.拋出異常,而不是採用盜夢空間式的嵌套(Inception-Style Nesting)
多層次的嵌套是醜陋的、難以維護和不可讀的。下面的代碼是個簡單的例子,但是隨着時間的推移會變得更糟:
-
- $error_message = null;
- if ($this->form_validation->run())
- {
- if ($this->upload->do_upload())
- {
- $image = $this->upload->get_info();
- if ( ! $this->image->create_thumbnail($image['file_name'], 300, 150))
- {
- $error_message = 'There was an error creating the thumbnail.';
- }
- }
- else
- {
- $error_message = 'There was an error uploading the image.';
- }
- }
- else
- {
- $error_message = $this->form_validation->error_string();
- }
-
- if ($error_message !== null)
- {
- $this->load->view('form', array(
- 'error' => $error_message,
- ));
- }
-
- else
- {
- $some_data['image'] = $image['file_name'];
- $this->some_model->save($some_data);
- }
如此凌亂的代碼,是否該整理下呢。建議大家使用異常這個清潔劑:
- try
- {
- if ( ! $this->form_validation->run())
- {
- throw new Exception($this->form_validation->error_string());
- }
- if ( ! $this->upload->do_upload())
- {
- throw new Exception('There was an error uploading the image.');
- }
- $image = $this->upload->get_info();
- if ( ! $this->image->create_thumbnail($image['file_name'], 300, 150))
- {
- throw new Exception('There was an error creating the thumbnail.');
- }
- }
-
- catch (Exception $e)
- {
- $this->load->view('form', array(
- 'error' => $e->getMessage(),
- ));
-
- return;
- }
-
- $some_data['image'] = $image['file_name'];
- $this->some_model->save($some_data);
雖然代碼行數並未改變,但它擁有更好的可維護性和可讀性。儘量保持代碼簡單。
9.False——Happy方法
Ruby或Python開發者常常關注一些微小的異常,這是相當不錯的事情。如果有地方出錯就會拋出異常並且你會立即知道問題所在。
在PHP中,特別是使用比較老的框架,如CodeIgniter,與拋出異常相比,它僅僅返回一個flase值,並且把錯誤字符串分配給其他一些屬性。這就驅使你使用get_error()方法。
Exception-happy遠遠好於false-happy。如果代碼裏面存在錯誤(例如不能連上S3下載圖片,或者值爲空等),然後拋出一個異常,你也可以通過繼承Exception類來拋出特定的異常類型,例如:
- class CustomException extends Exception {}
拋出自定義類型異常會讓調試變得更加容易。
10.Use Guard Clauses
使用if語句控制函數或方法的執行路徑是很常見的事情,如果if條件爲true就執行if裏面的代碼,否則就執行else裏面的代碼。例如下面這段代碼:
- function someFunction($param) {
- if ($param == 'OK') {
- $this->doSomething();
- return true;
- } else {
- return false;
- }
- }
這是很常見的意大利麪條式的代碼,通過轉換條件對上述代碼進行優化,不僅可以增加其可讀性,看起來還會更加簡單,如下:
- function someFunction($param) {
- if ($param != 'OK') return false;
- $this->doSomething();
- return true;
- }
11.使用While進行簡單的迭代
使用for進行循環是很常見的事情:
- for (var i = 0; i < x; i++) {
- ...
- }
當然,for循環也有許多優勢,但是對於一些的循環,使用while或許會更好:
- var i = x;
- while (i--) {
- ...
- }
12.保持方法可維護性
讓我們來看一下這個方法:
- class SomeClass {
- function monsterMethod() {
- if($weArePilots) {
- $this->goAndDressUp();
- $this->washYourTeeth();
- $this->cleanYourWeapon();
- $this->takeYourHelmet();
- if($this->helmetDoesNotFit())
- $this->takeAHat();
- else
- $this->installHelmet();
- $this->chekcYourKnife();
- if($this->myAirplain() == "F22")
- $this->goToArmyAirport();
- else
- $this->goToCivilianAirport();
- $this->aim();
- $this->prepare();
- $this->fire();
- }
- }
- }
再看如下代碼:
- class SomeClass {
- function monsterMethod() {
- if($weArePilots) {
- $this->prepareYourself();
- $this->tryHelmet();
- $this->findYourAirport();
- $this->fightEnemy();
- }
- }
- private function prepareYourself() {
- $this->goAndDressUp();
- $this->washYourTeeth();
- $this->cleanYourWeapon();
- $this->chekcYourKnife();
- }
- private function tryHelmet() {
- $this->takeYourHelmet();
- if($this->helmetDoesNotFit())
- $this->takeAHat();
- else
- $this->installHelmet();
- }
- private function findYourAirport() {
- if($this->myAirplain() == "F22")
- $this->goToArmyAirport();
- else
- $this->goToCivilianAirport();
- }
- private function fightEnemy() {
- $this->aim();
- $this->prepare();
- $this->fire();
- }
- }
對比兩段代碼,第二段代碼更加簡潔、可讀和可維護。
13.避免深層嵌套
太多層的嵌套會讓代碼很難閱讀、理解和維護。看看下面的代碼:
- function doSomething() {
- if ($someCondition) {
- if ($someOtherCondition) {
- if ($yetSomeOtherCondition) {
- doSomethingSpecial();
- }
- doSomethingElse();
- }
- }
- }
條件裏面又嵌套多個條件,通過轉換條件,我們對代碼進行了調整:
- function doSomething() {
- if (!$someCondition) {
- return false;
- }
- if (!$someOtherCondition) {
- return false;
- }
- if ($yetSomeOtherCondition) {
- doSomethingSpecial();
- }
- doSomethingElse();
- }
相對於前面的代碼,這段代碼簡潔了很多,並且所實現的功能也是一樣的。
當你在if裏面使用嵌套,請仔細檢查代碼,裏面可能同時執行多個方法,例如下面這段代碼:
- function someFunc() {
- if($oneThing) {
- $this->doSomething();
- if($anotherThing)
- $this->doSomethingElse();
- }
- }
這種情況下,可以把嵌套代碼提取出來:
- function someFunc() {
- if($oneThing) {
- $this->doSomething();
- $this->doAnotherThing($anotherThing);
- }
- }
- private doAnotherThing($anotherThing) {
- if($anotherThing)
- $this->doSomethingElse();
- }
14.避免使用匿名數字和字符串(Avoid Magic Numbers and Strings)
使用匿名數字和字符串是有害無益的,在代碼裏定義需要使用的變量和常量。比如下面這段代碼:
- function someFunct() {
- $this->order->set(23);
- $this->order->addProduct('superComputer');
- $this->shoppingList->add('superComputer');
- }
給23和“superComputer”賦予相應意義的變量名:
- function someFunct() {
- $orderId = 23;
- $selectedProductName = 'superComputer';
- $this->order->set($orderId);
- $this->order->addProduct($selectedProductName);
- $this->shoppingList->add($selectedProductName);
- }
可能會有人認爲,一些無意義的變量儘量少定義,雖然它們對性能的影響是微不足道的。但可讀性永遠處於優先地位。請記住:不要隨便優化性能,除非你知道爲什麼。
15.使用Built-in數組函數
使用built-in函數來代替foreach()
差的代碼:
- foreach (&$myArray as $key =>$element) {
- if ($element > 5) unset ($myArray[$key]);
- }
改進後的代碼:
- $myArray = array_filter($myArray, function ($element) { return $element <= 5;});
PHP裏面提供了許多數組方法。起初會混淆,但是試着花時間好好學學它們。
16.不要過度使用變量
大家在開發過程中很容易使用變量,但請記住,變量是需要存儲在內存中的。看下面這段代碼:
- public function get_posts() {
- $query = $this->db->get('posts');
- $result = $query->result();
- return $result;
- }
$result變量其實是不需要的。
- public function get_posts() {
- $query = $this->db->get('posts');
- return $query->result();
- }
雖然這些差別都是微不足道的,但對於養成良好的編碼習慣還是 很重要的。
通用篇
17.依賴數據庫引擎
使用數據庫來專門處理數據會讓你的程序更高效。
例如,在大多數情況下,你可以避免冗餘的數據查詢。大多數的plug-and-play用戶管理腳本在用戶註冊時都使用了兩次數據查詢:先檢查用戶名/郵件是否存在,另外再把用戶信息插入到數據庫中。一個比較好的做法是在數據庫中設置username字段爲UNIQUE,然後你可以利用本地的MySQL函數來檢查用戶名是否存在,然後添加進去。
18.正確命名變量
使用x、y、z命名變量的時代已經結束(除非是處理一個座標系統)。變量是你邏輯代碼的重要組成部分。不想鍵入長名字嗎?獲取一個好的IDE吧,使用IDE只需一眨眼的功夫就可以完成變量命名。
19.方法表示動作
見名知意,看到方法名字就知道它執行了哪些動作。使用一個短的,但具有描述性的範圍命名(例如:public methods即可這樣命名);使用一個長的名字,並且可以更加詳細的描述(例如:定義private/protected methods)。這樣會讓你的代碼更加可讀可寫。
當然也要避免用非英語來進行命名。例如使用“做些什麼()”或者делатьчтото()命名,簡直是糟透了的命名。對於其他程序員來說,真的很難理解。尤其是在一個團隊裏,請記住,讓你命名更加規範些吧!
20.結構的定義
最後,我們來說一下代碼結構,從可讀性和可維護性來講,代碼結構也是相當重要的,下面我們從兩方面來講:
-
首行縮進4字節或2個標籤寬度。
-
設置合理的線寬(line-width)並且保持。一行只有40個字節?我們已經不是70年代的人了。一行限制在120個字節,並且在屏幕上放一個標籤,並且驅使IDE保持。
結論
發生錯誤不要緊,關鍵是要總結錯誤,並且從中吸取教訓,只有不斷總結和學習,才能讓你的編程之路走的更遠。