编写超级可读代码的15个最佳实践
?
?????? 一月两次,我们重温Nettuts历史上读者最喜欢的文章。
?????? 代码可读性是一个计算机编程世界的普遍主题。它是我们作为开发者第一件学习的事情。这篇文章将阐述编写可读性代码十五个最重要的最佳实践。
--------------------------------------------
1 注释和文档
?????? 集成开发环境IDE在过去的短短几年里走过了很长的路。它使得注释代码比以前更加有用。依照特定标准书写的注释允许IDE和其他工具通过不同的方式来使用它们。
?????? 考虑如下示例:
?
?????? 我在函数定义中添加的注释可以在调用它的地方看到,即便是在其他文件中。
?????? 这里是我另外一个从第三方库中调用函数的例子:
?
?????? 在这些特殊的例子中,使用的注释(或者文档)类型基于PHPDoc,IDE是Aptana。
--------------------------------------------
2 一致的排版
?????? 我假定你已经知道了你必须要缩进你的代码。然而,保持排版样式一致仍然是一个好主意。
?????? 这里有不止一种方式来进行代码排版。
第一种:
view plaincopy to clipboardprint?
function foo() {??
??? if ($maybe) {??
??????? do_it_now();??
??????? again();??
??? } else {??
??????? abort_mission();??
??? }??
??? finalize();??
}?
function foo() {
?if ($maybe) {
??do_it_now();
??again();
?} else {
??abort_mission();
?}
?finalize();
}
第二种:
view plaincopy to clipboardprint?
function foo()??
{??
??? if ($maybe)??
??? {??
??????? do_it_now();??
??????? again();??
??? }??
??? else?
??? {??
??????? abort_mission();??
??? }??
??? finalize();??
}?
function foo()
{
?if ($maybe)
?{
??do_it_now();
??again();
?}
?else
?{
??abort_mission();
?}
?finalize();
}
第三种:
view plaincopy to clipboardprint?
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样式:
view plaincopy to clipboardprint?
function foo()??
{???????????????????? // placed on the next line??
??? if ($maybe) {???? // placed on the same line??
??????? do_it_now();??
??????? again();??
??? } else {??
??????? abort_mission();??
??? }??
??? finalize();??
}?
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 避免显而易见的注释
?????? 为代码添加注释是效果显著的;但是,它可能太过或者只是多余的文本。像如下例子:
view plaincopy to clipboardprint?
// 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();??
}?
// 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();
}??
?????? 如果注释内容都是显而易见的,它们并没有提高工作效率。如果你必须要注释这些代码,你可以简单的把它们合并在一行:
view plaincopy to clipboardprint?
// display state selection for US users??
$country_code = get_country_code($_SERVER['REMOTE_ADDR']);??
if ($country_code == 'US') {??
??? 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 代码分组
?????? 确定的任务多半需要多行代码。使用一些空白将这些任务的代码分隔为几段是一个好主意。
?????? 这是一个简单的示例:
view plaincopy to clipboardprint?
// 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');?
// 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里大多采用下划线命名法。
?????? 它们也可以混用。一些开发者喜欢在程序函数和类名上使用下划线命名,但是在类方法名上使用骆驼命名。
view plaincopy to clipboardprint?
class Foo_Bar {??
??? public function someDummyMethod() {??
??? }??
}??
function procedural_function_name() {??
}?
class Foo_Bar {
?public function someDummyMethod() {
?}
}
function procedural_function_name() {
}
?????? 所以,没有明显的“最好的”样式,只需要保持一致。
--------------------------------------------
6 DRY原则
?????? DRY即不要重复你自己。也被称为DIE:重复是恶魔。
?????? 这个原则规定:
????? “在一个系统里每一个知识的片段必须有一个单一、明确、权威的表现。”
?????? 大多数应用程序(或者通常的计算机)的目的是让重复的任务自动化。这个原则在所有的代码,即使Web程序中也应该保持。代码的相同片段不应该多次重复。
?????? 例如,大多数Web程序由许多页面组成。这些页面很可能包含相同的元素。页头和页脚经常符合这个条件。复制和粘贴这些页头和页尾到每一个页面中不是一个好主意。这是Jeffrey Way解释如何在CodeIgniter里创建模版的链接。
view plaincopy to clipboardprint?
$this->load->view('includes/header');??
$this->load->view($main_content);??
$this->load->view('includes/footer');?
$this->load->view('includes/header');
$this->load->view($main_content);
$this->load->view('includes/footer');
--------------------------------------------
7 避免过深的嵌套
?????? 太多层的嵌套会造成代码阅读和跟踪困难。
view plaincopy to clipboardprint?
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)) {
??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;
?}
}
?????? 为了可读性,通常需要修改代码来减少嵌套的层数。
view plaincopy to clipboardprint?
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;??
??? }??
}?
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 减少行的长度
?????? 我们的眼睛对于阅读高和窄的文本列更感觉舒适。这就是为什么报纸文章看起来像如下样子的原因:
?
?????? 避免在一行上编写过长的代码是一个最佳实践。
view plaincopy to clipboardprint?
// 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'";?
// 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 统一的临时变量名
?????? 通常,变量名应该是描述性的并且包含一个或者更多的单词。但是,这对临时变量来说并不是必须的。它们可以短到只有一个单独字符。
?????? 最佳实践是:对于有同样职责临时变量采用统一的命名。这里有一些我倾向于在代码里使用的例子:
view plaincopy to clipboardprint?
// $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');?
// $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关键词和函数名是大小写无关的,大写来将它们从表名和列名中区分出来是一个通用的实践。
view plaincopy to clipboardprint?
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?
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框架:
CodeIgniter
Zend Framework
Cake PHP
Symfony
?????? 流行的模版引擎:
Smarty
Dwoo
Savant
?????? 流行的CMS系统:
Joomla
Drupal
--------------------------------------------
13 模版内的交替格式
?????? 你可以选择不使用一个奇特的模版引擎,取而代之的是在模版文件里使用纯内联的PHP代码。这不是必须要违反“数据和代码分离“,只是内联代码是直接和输出相关的,并且可读。在这种情况下你可以考虑使用交替格式来控制结构。
?????? 这是一个示例:
view plaincopy to clipboardprint?
<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; ?>?
<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 面向程序
?????? 面向对象编程可以帮助你创建结构化代码。但是这不代表你完全排除程序化编程。事实上创建两者混合的风格是非常棒的。
?????? 描述数据,通常是数据库里的数据,必须使用对象。
view plaincopy to clipboardprint?
class User {??
??? public $username;??
??? public $first_name;??
??? public $last_name;??
??? public $email;??
??? public function __construct() {??
??????? // ...??
??? }??
??? public function create() {??
??????? // ...??
??? }??
??? public function save() {??
??????? // ...??
??? }??
??? public function delete() {??
??????? // ...??
??? }??
}?
class User {
?public $username;
?public $first_name;
?public $last_name;
?public $email;
?public function __construct() {
??// ...
?}
?public function create() {
??// ...
?}
?public function save() {
??// ...
?}
?public function delete() {
??// ...
?}
}
?????? 程序化方法常用于可以独立执行的特定任务。
view plaincopy to clipboardprint?
function capitalize($string) {??
??? $ret = strtoupper($string[0]);??
??? $ret .= strtolower(substr($string,1));??
??? return $ret;??
}?
function capitalize($string) {
?$ret = strtoupper($string[0]);
?$ret .= strtolower(substr($string,1));
?return $ret;
}
--------------------------------------------
15 阅读开源代码
?????? 开源项目是许多开发者一起构建的。这些项目必须保持高度的代码可读性,以便他们可以尽可能高效的协同工作。
?????? 因此,通读这些项目的源代码来观察这些开发者是如何工作的是非常棒的方法。
?
?
--------------------------------------------
16 代码重构
?????? 当你“重构“,你在不改变功能的情况下调整代码。你可以把它看作是“清理”,为了改进代码质量和可读性。
?????? 这并不包括bug的修复或者添加新功能。你可以重构你之前编写的代码,当它们在你头脑你还保持新鲜的时候,以便于你两个月以后有可能回顾代码时更加可读和可重用。就像那句格言所说的一样:“尽早重构,经常重构“。
?????? 你可以在重构期间应用以上任何关于代码可读性的“最佳实践“。我希望你喜欢这篇文章!我遗忘了什么?请通过回复告知我。
?