編寫超級可讀程式碼的15個最佳實踐

蔣宇捷發表於2012-08-12

  程式碼可讀性是一個計算機程式設計世界的普遍主題。它是我們作為開發者第一件學習的事情。這篇文章將闡述編寫可讀性程式碼十五個最重要的最佳實踐。

1 – 註釋和文件

  整合開發環境IDE在過去的短短几年裡走過了很長的路。它使得註釋程式碼比以前更加有用。依照特定標準書寫的註釋允許IDE和其他工具通過不同的方式來使用它們。

  考慮如下示例:

  我在函式定義中新增的註釋可以在呼叫它的地方看到,即便是在其他檔案中。

  這裡是我另外一個從第三方庫中呼叫函式的例子:

  在這些特殊的例子中,使用的註釋(或者文件)型別基於PHPDoc,IDE是Aptana


2 – 一致的排版

  我假定你已經知道了你必須要縮排你的程式碼。然而,保持排版樣式一致仍然是一個好主意。

  這裡有不止一種方式來進行程式碼排版。

  第一種:
function foo() {
	if ($maybe) {
		do_it_now();
		again();
	} else {
		abort_mission();
	}
	finalize();
}
  第二種:
function foo()
{
	if ($maybe)
	{
		do_it_now();
		again();
	}
	else
	{
		abort_mission();
	}
	finalize();
}
  第三種:
function foo()
{	if ($maybe)
	{	do_it_now();
		again();
	}
	else
	{	abort_mission();
	}
	finalize();
}

  我曾經使用第二種樣式但是最近換為第一種。但是這僅僅只代表了一種偏愛。這裡並沒有每個人必須要遵守的“最好的”樣式。事實上,最佳的樣式,就是一致的樣式。如果你是一個小組的一部分或者你在為一個專案貢獻程式碼,你必須依照這個專案之前使用的樣式。

  排版的樣式總不是完全和另外一個不同。有時,它們混合了多種不同的規則。例如,按照PEAR編碼標準,前括弧“{”和控制結構在同一行上,但是在功能定義後放在第二行上。

  PEAR樣式:

function foo()
{                     // placed on the next line
    if ($maybe) {     // placed on the same line
        do_it_now();
        again();
    } else {
        abort_mission();
    }
    finalize();
}

  同時注意它們使用4個空格而不是Tab來縮排。

  這裡有一個維基百科的文章,裡面有許多不同排版樣式的例子。


3 – 避免顯而易見的註釋

  為程式碼新增註釋是效果顯著的;但是,它可能太過或者只是多餘的文字。像如下例子:

// get the country code
$country_code = get_country_code($_SERVER['REMOTE_ADDR']);

// if country code is US
if ($country_code == 'US') {

	// display the form input for state
	echo form_input_state();
}

  如果註釋內容都是顯而易見的,它們並沒有提高工作效率。如果你必須要註釋這些程式碼,你可以簡單的把它們合併在一行:

// display state selection for US users
$country_code = get_country_code($_SERVER['REMOTE_ADDR']);
if ($country_code == 'US') {
	echo form_input_state();
}

4 – 程式碼分組

  確定的任務多半需要多行程式碼。使用一些空白將這些任務的程式碼分隔為幾段是一個好主意。

  這是一個簡單的示例:

// get list of forums
$forums = array();
$r = mysql_query("SELECT id, name, description FROM forums");
while ($d = mysql_fetch_assoc($r)) {
	$forums []= $d;
}
// load the templates
load_template('header');
load_template('forum_list',$forums);
load_template('footer');

  在每一段之前新增註釋也增強了視覺上的分隔。


5 – 命名的一致性

  PHP有些時候在遵守命名一致性方面有很大問題:

  • strops()和str_split()
  • imagetypes()和image_type_to_extension()

       首先,這些命名必須有單詞的分界線。有兩種流行的選擇:

  • 駱駝命名法:除了第一個單詞外,每個單詞的第一個字元大寫。
  • 下劃線命名法: 單詞間採用下劃線,例如mysql_real_escape_string()。

  像我之前提到的一樣,採用不同的命名選擇會建立和排版樣式類似的情形。如果一個已有的專案遵照一個確定的習慣,你必須遵守它。同時,某些語言平臺傾向於使用特定的命名規則。例如Java裡,大多數程式碼使用駱駝命名法;在PHP裡大多采用下劃線命名法。

  它們也可以混用。一些開發者喜歡在程式函式和類名上使用下劃線命名,但是在類方法名上使用駱駝命名。

class Foo_Bar {
	public function someDummyMethod() {

	}
}
function procedural_function_name() {
}

  所以,沒有明顯的“最好的”樣式,只需要保持一致。


6 – DRY原則

  DRY即不要重複你自己。也被稱為DIE:重複是惡魔。

  這個原則規定:

  “在一個系統裡每一個知識的片段必須有一個單一、明確、權威的表現。”

  大多數應用程式(或者通常的計算機)的目的是讓重複的任務自動化。這個原則在所有的程式碼,即使Web程式中也應該保持。程式碼的相同片段不應該多次重複。

  例如,大多數Web程式由許多頁面組成。這些頁面很可能包含相同的元素。頁頭和頁尾經常符合這個條件。複製和貼上這些頁頭和頁尾到每一個頁面中不是一個好主意。這是Jeffrey Way解釋如何在CodeIgniter裡建立模版的連結

$this->load->view('includes/header');
$this->load->view($main_content);
$this->load->view('includes/footer');

7 – 避免過深的巢狀

       太多層的巢狀會造成程式碼閱讀和跟蹤困難。

function do_stuff() {
// ...
	if (is_writable($folder)) {
		if ($fp = fopen($file_path,'w')) {
			if ($stuff = get_some_stuff()) {
				if (fwrite($fp,$stuff)) {
					// ...
				} else {
					return false;
				}
			} else {
				return false;
			}
		} else {
			return false;
		}
	} else {
		return false;
	}
}

  為了可讀性,通常需要修改程式碼來減少巢狀的層數。

function do_stuff() {
// ...
	if (!is_writable($folder)) {
		return false;
	}
	if (!$fp = fopen($file_path,'w')) {
		return false;
	}
	if (!$stuff = get_some_stuff()) {
		return false;
	}
	if (fwrite($fp,$stuff)) {
		// ...
	} else {
		return false;
	}
}

8 – 減少行的長度

  我們的眼睛對於閱讀高和窄的文字列更感覺舒適。這就是為什麼報紙文章看起來像如下樣子的原因:

  避免在一行上編寫過長的程式碼是一個最佳實踐。

// bad
$my_email->set_from('test@email.com')->add_to('programming@gmail.com')->set_subject('Methods Chained')->set_body('Some long message')->send();

// good
$my_email
	->set_from('test@email.com')
	->add_to('programming@gmail.com')
	->set_subject('Methods Chained')
	->set_body('Some long message')
	->send();

// bad
$query = "SELECT id, username, first_name, last_name, status FROM users LEFT JOIN user_posts USING(users.id, user_posts.user_id) WHERE post_id = '123'";

// good
$query = "SELECT id, username, first_name, last_name, status
	FROM users
	LEFT JOIN user_posts USING(users.id, user_posts.user_id)
	WHERE post_id = '123'";

  同時,如果任何人想要在例如Vim這樣的終端視窗中閱讀程式碼,限制每一行的長度在80個字元以內是一個好主意。


9 – 程式碼結構

  理論上,你可以將整個應用程式碼寫在一個檔案裡。但是對於閱讀和維護來說是一個噩夢。

  在我的第一個程式設計專案中,我知道建立“包含檔案”的含義。但是,我並沒有好好進行組織。我建立了一個“inc”資料夾,放置了兩個檔案:db.php、functions.php。當程式變大時,functions檔案也變得越來越大並難以維護。

  最好的方法之一是採用框架或者模仿它們的資料夾結構。下面是CodeIgniter的檔案結構:


10 – 統一的臨時變數名

  通常,變數名應該是描述性的並且包含一個或者更多的單詞。但是,這對臨時變數來說並不是必須的。它們可以短到只有一個單獨字元。

  最佳實踐是:對於有同樣職責臨時變數採用統一的命名。這裡有一些我傾向於在程式碼裡使用的例子:

// $i for loop counters
for ($i = 0; $i < 100; $i++) {

	// $j for the nested loop counters
	for ($j = 0; $j < 100; $j++) {

	}
}

// $ret for return variables
function foo() {
	$ret['bar'] = get_bar();
	$ret['stuff'] = get_stuff();

	return $ret;
}

// $k and $v in foreach
foreach ($some_array as $k => $v) {

}

// $q, $r and $d for mysql
$q = "SELECT * FROM table";
$r = mysql_query($q);
while ($d = mysql_fetch_assocr($r)) {

}

// $fp for file pointers
$fp = fopen('file.txt','w');

11 – SQL關鍵詞大寫

  資料庫互動對於大多數Web應用來說是很大一個組成部分。如果你正在編寫SQL查詢,儘量保持它們可讀。

  即使SQL關鍵詞和函式名是大小寫無關的,大寫來將它們從表名和列名中區分出來是一個通用的實踐。

SELECT id, username FROM user;

UPDATE user SET last_login = NOW()
WHERE id = '123'

SELECT id, username FROM user u
LEFT JOIN user_address ua ON(u.id = ua.user_id)
WHERE ua.state = 'NY'
GROUP BY u.id
ORDER BY u.username
LIMIT 0,20

12 – 程式碼和資料分離

  這是另外一個對於所有環境下的絕大多數程式語言都適用的原則。在Web開發中,資料通常意味著HTML輸出。

  當PHP許多年前第一次釋出時,它最開始被看作是一個模版引擎。在巨大的HTML檔案裡插入一些PHP程式碼行是非常普通的。但是,這些年來,事情發生了改變:網站變得越來越動態化和功能化。程式碼已經是Web程式的一個很大的部分,將它們和HTML合併在一起並不是一個好的實踐。

  你可以在你的程式中應用這個原則,或者你可以使用一個第三方工具(模版引擎、框架或者CMS系統)或者依照它們的習慣。

  流行的PHP框架:

  流行的模版引擎:

  流行的CMS系統:

  • Joomla
  • Drupal

13 – 模版內的交替格式

  你可以選擇不使用一個奇特的模版引擎,取而代之的是在模版檔案裡使用純內聯的PHP程式碼。這不是必須要違反“資料和程式碼分離“,只是內聯程式碼是直接和輸出相關的,並且可讀。在這種情況下你可以考慮使用交替格式來控制結構。

  這是一個示例:

<div class="user_controls">
	<?php if ($user = Current_User::user()): ?>
		Hello, <em><?php echo $user->username; ?></em> <br/>
		<?php echo anchor('logout', 'Logout'); ?>
	<?php else: ?>
		<?php echo anchor('login','Login'); ?> |
		<?php echo anchor('signup', 'Register'); ?>
	<?php endif; ?>
</div>

<h1>My Message Board</h1>

<?php foreach($categories as $category): ?>

	<div class="category">

		<h2><?php echo $category->title; ?></h2>

		<?php foreach($category->Forums as $forum): ?>

			<div class="forum">

				<h3>
					<?php echo anchor('forums/'.$forum->id, $forum->title) ?>
					(<?php echo $forum->Threads->count(); ?> threads)
				</h3>

				<div class="description">
					<?php echo $forum->description; ?>
				</div>

			</div>

		<?php endforeach; ?>

	</div>

<?php endforeach; ?>

  這讓你避免了許多大括號。同時程式碼看起來和HTML的結構和排版相似。


14 – 物件導向 vs 面向程式

  物件導向程式設計可以幫助你建立結構化程式碼。但是這不代表你完全排除程式化程式設計。事實上建立兩者混合的風格是非常棒的。

  描述資料,通常是資料庫裡的資料,必須使用物件。

class User {
	public $username;
	public $first_name;
	public $last_name;
	public $email;

	public function __construct() {
		// ...
	}

	public function create() {
		// ...
	}

	public function save() {
		// ...
	}

	public function delete() {
		// ...
	}
}

  程式化方法常用於可以獨立執行的特定任務。

function capitalize($string) {

	$ret = strtoupper($string[0]);
	$ret .= strtolower(substr($string,1));
	return $ret;

}

15 – 閱讀開原始碼

  開源專案是許多開發者一起構建的。這些專案必須保持高度的程式碼可讀性,以便他們可以儘可能高效的協同工作。

  因此,通讀這些專案的原始碼來觀察這些開發者是如何工作的是非常棒的方法。


16 – 程式碼重構

  當你“重構“,你在不改變功能的情況下調整程式碼。你可以把它看作是“清理”,為了改進程式碼質量和可讀性。

  這並不包括bug的修復或者新增新功能。你可以重構你之前編寫的程式碼,當它們在你頭腦你還保持新鮮的時候,以便於你兩個月以後有可能回顧程式碼時更加可讀和可重用。就像那句格言所說的一樣:“儘早重構,經常重構“。

  你可以在重構期間應用以上任何關於程式碼可讀性的“最佳實踐“。我希望你喜歡這篇文章!我遺忘了什麼?請通過回覆告知我。

譯自:top-15-best-practices-for-writing-super-readable-code

相關文章