Thành viên
 
 
 
RAIN1007HT
Đăng ký ngày:
12/11/2009
Bài viết: 1
Gửi tin nhắn
21 Lỗi chết người trong PHP (Phần 1)
  Trung bình: 2.7 (Đã có 17 lượt đánh giá)
   13/11/2009 07:03 AM

Bài viết này dành cho những lập trình viên PHP quan tâm đến việc tránh những lỗi thường gặp khi sử dụng PHP. Người đọc phải biết các cú pháp của PHP, và nên biết công dụng của các hàm trong PHP. 

Một trong những điểm mạnh nhất của PHP vô tình trở thành một trong những điểm yếu nhất của nó: tính dễ sử dụng. Nhiếu người chọn PHP vì tính dễ sử dụng, đã không nhận ra rằng: sử dụng đúng PHP còn khó hơn các ngôn ngữ khác.

Trong loạt bài này, tôi sẽ lần lượt nêu ra 21 lỗi, từ những sai lầm giáo khoa (làm script trở nên chậm và khó quản lí) đến những sai lầm chết người - có thể xem là nguồn gốc của những sai lầm sơ đẳng.



Phần 1: 7 lỗi giáo khoa



1. Sử dụng printf() không thích hợp
Hàm printf() dùng để in dữ liệu có định dạng

Nó có thể được dùng, thí dụ, khi bạn một in một số kiểu double với 2 số lẻ, hoặc trong bất kì tình huống nào bạn muốn thay đổi định dạng trước khi in.

Thí dụ dưới đây minh hoạ cách dùng đúng của printf(): định dạng số Pi với độ chính xác theo ý muốn

Mã lệnh (PHP)

<?
/*
* The three faces of Π
*/

printf ("Pi is: %.2f\n<br>\n", M_PI);
printf ("Pi is also: %.3f\n<br>\n", M_PI);
printf ("Pi is also: %.4f\n<br>\n", M_PI);
?>



Chú ý. Tôi đã từng gặp những người sợ dùng printf(), thay vào đó lại dùng những hàm định dạng tự viết, dài đến 30-40 dòng, trong đi một câu printf() có thể làm mọi thứ anh ta mong muốn.

Nhiếu lập trình viên dùng sai printf(): in các biến, các giá trị trả về của hàm hoặc thỉnh thoảng, chỉ là dữ liệu thông thường. Thường xảy ra trong hai tình huống:

Câu lệnh print() thích hợp hơn
Hiển thị giá trị trả về của một hàm
1. Khi nào print() thích hợp hơn?
Các lập trình viên thường sử dụng printf() trong khi chỉ print() là đủ. Xét thí dụ sau:

Mã lệnh (PHP)

<?
$name = 'Sterling Hughes';
$job = 'Senior Engineer';
$company = 'DesignMultimedia';
$email = 'shughes@designmultimedia.com';

printf ("My name is: %s\n<br>\n
My Job is: %s, %s\n<br>\n
My E-mail is: %s\n<br>\n",
$name, $job, $company, $email);
?>


Hàm print() có thể dùng thay cho printf() như sau:


print "My Name is: $name\n<br>\n
My Job is: $job, $company\n<br>\n
My E-mail is: $email\n<br>\n";



Khi không cần định dạng sử liệu, dùng print() thay cho printf() có những lợi ích sau:

Thi hành nhanh hơn: hàm printf() định dạng dữ liệu của bạn trước khi hiển thị, nó sẽ chậm hơn print() hoặc echo()
Mã sáng sủa: hãy xem, dùng hàm printf() sẽ làm cho người đọc hơi bị lẫn lộn (tất nhiên trừ khi họ có nền tảng C). Nó đòi hỏi kiến thức về cú pháp printf() (thí dụ, %s thay cho chuỗi còn %d là số) và về kiểu biến
2. Dùng printf() để xuất dữ liệu trả về từ gọi hàm
Một lỗi thường gặp khác là dùn printf() để xuất dữ liệu trả về từ gọi hàm, thí dụ như hàm đếm dưới đây:

Mã lệnh (PHP)


<?
printf ("%d occurrences of %s found.",
count($result), $search_term);
?>



Khi xuất giá trị do hàm trả về, toán tử . nên dùng để nối trong print(), như dưới đây:


<?
print count($result) .
" occurrences of $search_term found.";
?>


Dùng toán tử . nhanh hơn việc dùng printf()

2. Áp dụng sai ngữ nghĩa (semantics)

Nhiều lập trình viên sử dụng PHP mà không biết đến những điểm tinh tế của ngôn ngữ này. Một trong những điểm đó là sự khác nhau giữa cú pháp (syntax) và ngữ nghĩa (semantics).

Cú pháp: những quy tắc định nghĩa một phần tử. Thí dụ: dấu $ để trước dùng định nghĩa biến, dùng dấu () và các tham số định nghĩa một hàm...
Ngữ nghĩa: những quy tắc áp dụng trong cú pháp. Thí dụ: một hàm có 2 biến định nghĩa bởi cú pháp của nó, hai biến này có kiểu string - đó là ngữ nghĩa.
Trong một ngôn ngữ lỏng lẻo như PHP, bạn có nhiều lựa chọn để viết lệnh. Các biến không cần có kiểu xác định...

Thí dụ sau mở tập tin và in từng dòng:

<?
$fp = @fopen ('somefile.txt', 'r')
or die ('Cannot open somefile.txt');
while ($line = @fgets ("$fp", 1024)) // error
{
print $line;
}

@fclose ("$fp") // error
or die ('Cannot close somefile.txt');
?>


Thí dụ trên sẽ tạo lỗi

Warning: Supplied argument is not a valid File-Handle resource in C:\Inetpub\wwwroot\tst.php on line 4.

Đó là do biến $fp đặt trong dấu nháy kép nên được chuyển thành chuỗi. Thế mà hàm fopen() nhận một định danh tài nguyên (resource identifier) trong tham số đầu của nó, chứ không nhận một chuỗi. Để giải quyết vấn đề, bạn chỉ đơn giản bỏ dấu nháy kép đi


<?
$fp = @fopen ('somefile.txt', 'r')
or die ('Cannot open somefile.txt');

while ($line = @fgets ($fp, 1024))
{
print $line;
}

@fclose ($fp)
or die ('Cannot close somefile.txt');
?>


1. Có thể tránh việc áp dụng sai ngữ nghĩa?

Our example above generated an error statement. But PHP enables you to customize your scripts to fit a unique scenario or output requirement. So, it is at least theoretically possible to "get away" with misapplying a semantic. Tôi không hiểu, nhưng dịch thế này được không?

Thí dụ trên của chúng ta tạo ra một thông báo lỗi. Nhưng PHP cho phép bạn tuỳ biến các script để thích hợp với một kịch bản khác thường hoặc với các đòi hỏi của thông tin ra. Do đó, ít nhất trên lí thuyết, bạn có khả năng tránh việc áp dụng sai ngữ nghĩa.

Vậy, bạn cần biết những hậu quả có thể có (possible outcomes) nếu bạn quyết định học về ngữ nghĩa. Áp dụng sai dẫn đến những lỗi khá tinh vi nếu bạn không chú ý.

Nếu bạn muốn tuỳ biến script, bạn cần hiểu những chủ đề chính sau:
  • Kiểu: trong PHP, mỗi biến có một kiểu xác định ở một thời điểm xác định, cho dù bạn có thể tự do chuyển đổi kiểu một biến. Nói một cách khác, không có biến nào lại không kèm theo tính chất của kiểu của nó. PHP có 7 kiểu cơ bản: boolean, resource, integer, double, string, array và object.
  • Tầm vực: trong PHP, mỗi biến có một tầm vực riêng. Tầm vực biến quy định biến có thể được truy cập từ đâu, tồn tại trong thời gian nào. Hiểu sai khái niệm cơ bản về "tầm vực" dẫn đến những lỗi sai tinh tế và cả những lỗi lớn.
  • php.ini: khi viết một script chạy ở nhiều môi trường khác nhau, cần biết rằng không phải mọi cấu hình PHP đều như nhau. Do đó, cần thiết những lệnh kiểm tra để đảm bảo script của bạn chạy tốt trong cấu hình PHP của người khác.
3. Thiếu ghi chú

Theo ý tôi, mã nguồn thiếu ghi chú là căn nguyên của sự lập trình ích kỉ. Nó dẫn tới những hiệu chỉnh sai lầm, hiểu sai ý nghĩa và làm người đọc mệt mỏi. Nói chung, lập trình ghi chú (inline documentation) được mọi người khẳng nhận là điều tốt, nhưng hiếm khi nó tồn tại.

Một vấn đề khác là quá nhiều ghi chú. Dù hiếm gặp, nhưng nó làm cho các đoạn mã bị cắt vụn, gây ra sự khó theo dõi. Dưới đây là một thí dụ:

Mã lệnh (PHP)

<?
// Start off PHP code
$age = 18; // assign 18 to age
$age++; // increment $age by one

// print out introductory text
print "You are now 19, which means you have been:";
print "\n<br>\n<br>\n";

// A for loop to print out all of the
// previous ages
for ($idx = 0; $idx < $age; $idx++)
{
// Print out an individual age
print "$idx years old\n<br>\n";
}
// End the PHP code
?>


1. Bao nhiêu ghi chú thì đủ?

Nhiếu đến mức nào, điều đó tuỳ thuộc ngân sách của bạn, vào chính sách của công ty và vào độ phức tạp của chương trình. Tuy nhiên, cũng có một vài gợi ý cho bạn
  • Luôn có một mô tả ngắn về mục đích của hàm ngay trước định nghĩa của hàm đó
  • Thêm ghi chú vào những chỗ có thể bị hack, hoặc những chỗ tưởng rằng sai nhưng lại chạy đúng
  • Nếu một đoạn mã nào đó có thể gây nhầm lẫn, hãy thêm một ít ghi chú về mục đích của đoạn đó. Sau này bạn sẽ thấy được lợi ích của nó
  • Dùng một kiểu ghi chú nhất quán, /* */ hoặc là // (tránh dùng #)

Dưới đây là một thí dụ về ghi chú tốt

Mã lệnh (PHP)

<?
// Random_Numbers.lib
// Generate different types of random numbers.

mt_srand((double)microtime()*1000000);

//
// mixed random_element(array elements[, array weights])
// Extract a random element from elements. Weights is
// the relative probability that each element will be
// selected.
//

function random_element ($elements, $weights=array())
{

// There must be exactly the same amount of elements as
// there are weights for this algorithm to work properly

if (count ($weights) == count ($elements)) {
foreach ($elements as $element)
{
foreach ($weights as $idx)
{
// Note: we don't use $idx, since we
// don't want to override elements.
$randomAr[] = $element;
}
}
} else {
$randomAr = $elements;
}

$random_element = mt_rand (0, count ($randomAr)-1);
return $randomAr[ $random_element ];
}
?>

4. Nhiều biến, tốn nhiều thời gian

Có vài người bị ám ảnh bởi biến trung gian. Tôi không thể hiểu nổi tại sao ai đó có thể viết như thế này:

Mã lệnh (PHP)

<?
$tmp = date ("F d, h:i a"); /* ie January 3, 2:30 pm */
print $tmp;
?>


Tại sao phải dùng biến trung gian? Nó không cần thiết


Mã lệnh (PHP)
<?
print date ("F d, h:i a");
?>


Rủi thay, có vẻ như rất nhiều người khó bỏ được thói quen xấu này.

Biến tạm làm chậm thời gian thi hành chương tình của bạn. Tốt hơn là nên bỏ qua đó và gộp các lời gọi hàm với nhau. Những người dùng biến tạm thường làm chương của họ chạy chậm đến 25%.

Một lí do khác để tránh có quá nhiều biến tạm là vì trông nó không được đẹp mắt. Trong hai thí dụ trên, thí dụ nào súc tích hơn? Thí dụ nào làm con mắt dễ chịu hơn? Dùng quá nhiều biến tạm có thể dẫn đến mã chương trình khó đọc và không súc tích.

1. Lợi điểm của dùng biến tạm

Các biến tạm có lợi trong việc thay thế các hàm hay biểu thức dài lê thê. Nó có vai trò như bí danh giả. Điều này đặc biệt đúng khi bạn dùng một hàm hay biểu thức nhiều lần.

Xem xét thí dụ đây, nó không dùng nhiều biến hơn mức tối thiểu

Mã lệnh (PHP)

// string reverse_characters(string str)
// Reverse all of the characters in a string.
function reverse_characters ($str)
{
return implode ("", array_reverse (preg_split("//", $str)));
}


Nội dung trong hàm implode() dài và do đó khó đọc. Dùng một hoặc nhiều biến tạm có thể giúp chúng ta:

Mã lệnh (PHP)
// string reverse_characters(string str)
// Reverse all of the characters in a string.
function reverse_characters ($str)
{
$characters = preg_split ("//", $str);
$characters = array_reverse ($characters);

return implode ("", $characters);
}


2. Các luật chung của ngón tay cái

Khi quyết định có dùng biến tạm hoặc không, bạn nên suy nghĩ về 2 câu hỏi:

  • Bạn có dùng biến đó ít nhất hai lần?
  • Tính đọc được của mã có tăng đáng kể không?



Nếu ít nhất một câu trả lời là có, thì nên dùng biến tạm. Còn không, vứt nó đi và tổ hợp các hàm lại (nếu cần).

5. Viết lại các hàm có sẵn

Một số nơi phổ biến mã nguồn các script PHP chủ trương đổi tên các hàm sẵn có để tạo sự dễ dàng cho các lập trình viên chuyển từ VB sang. Thí dụ:

Mã lệnh (PHP)

<?
function len ($str)
{
return strlen ($str);
}
?>


Lại có một số người cố gắng viết lại các hàm PHP thông dụng thay vì đi học về hàm đó trong các tài liệu PHP cung cấp.

Có ít nhất 2 lí do để không nên làm điều này. Thứ nhất, và trên nhất, nó làm cho những người đọc (và sửa) chương trình của bạn khó hiểu và cảm thấy có quá nhiều hàm dư thừa. Họ tự hỏi tại sao bạn lại đi định nghĩa hàm theo kiểu đó, thay vì sử dụng các hàm định nghĩa sẵn bởi PHP.

Thứ hai, định nghĩa hàm như vậy cũng sẽ làm chậm chương trình của bạn (một cách không cần thiết). Không chỉ phải xử lí nhiều mã hơn, mà mỗi lần gọi hàm do bạn định nghĩa, bạn đã tốn thời gian cho chính hàm đó, trước khi hàm nguyên thuỷ được gọi.

1. Tránh viết lại các hàm có sẵn

Hãy đương đầu với nó. Đôi khi thật là khó để tránh chuyện này. Trước tiên, một lập trình viên không thể theo kịp các hàm của PHP ngay được. Và ai có thời gian mà tra cứu. Tại sao không viết lại cho khoẻ?

Cách làm của tôi là luôn có sẵn một tài liệu chỉ dẫn PHP (PHP manual) mỗi khi viết chương trình (tác giả bài này dùng một bản PDF có tạo chỉ mục, riêng tôi, người dịch, thì dùng một tài liệu CHM đầy đủ thông tin và có cả góp ý của người sử dụng mà bạn có thể lấy.. Sau đó, mỗi khi định viết một hàm mở rộng cho PHP, tôi đọc lướt qua tài liệu để xem hàm đó có chưa.

Tuy nhiên, cần chú ý là, do bản chất mã nguồn mở của PHP, bạn có thể tìm được các hàm do người dùng định nghĩa trước khi nó được thêm vào PHP (thí dụ như hàm tìm phần tử khác nhau giữa hai mảng). Điều này không có nghĩa là bạn phải hiệu chỉnh lại mã (This doesn't necessarily mean that you should have to correct the code. - don't understand)

6. Không tách biệt phần server và client

Vài lập trình viên cố kết nối cả chương trình với nhau, nghĩa là ghép chung mã HTML (client-side - phần khách) với mã PHP (server-side - phần chủ) vào trong một tập tin lớn.

Mặc dù điều này tốt cho các site nhỏ, nhưng nó có thể trở thành vấn đề lớn khi các site đó trở nên lớn hơn và được bổ sung thêm tính năng. Lập trình theo cách này làm nảy sinh vấn đề khó bảo trì và các tập tin trở nên cồng kềnh.

1. Hàm API

Khi muốn tách biệt phần khách - chủ, bạn có vài lựa chọn. Một cách là viết những hàm hiển thị nội dung linh động và đặt chúng đúng chỗ trong trang web.

Thí dụ dưới đây minh hoạ điều này:

index.php - phần khách

HTML
<?php include_once ("site.lib"); ?>
<html>
<head>
<title> <?php print_header (); ?> </title>
</head>
<body>
<h1> <?php print_header (); ?> </h1>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td width="25%">
<?php print_links (); ?>
</td>
<td>
<?php print_body (); ?>
</td>
</tr>
</table>
</body>
</html>


site.lib - phần chủ

Mã lệnh (PHP)
<?php
$dbh = mysql_connect ("localhost", "sh", "pass")
or die (sprintf ("Cannot connect to MySQL [%s]: %s", mysql_errno (), mysql_error ()));
@mysql_select_db ("MainSite")
or die (sprintf ("Cannot select database [%s]: %s", mysql_errno (), mysql_error ()));

$sth = @mysql_query ("SELECT * FROM site", $dbh)
or die (sprintf ("Cannot execute query [%s]: %s", mysql_errno (), mysql_error ()));

$site_info = mysql_fetch_object ($sth);

function print_header ()
{
global $site_info;
print $site_infheader;
}

function print_body ()
{
global $site_info;
print nl2br ($site_infbody);
}

function print_links ()
{
global $site_info;

$links = explode ("\n", $site_inflinks);
$names = explode ("\n", $site_inflink_names);

for ($i = 0; $i < count ($links); $i++)
{
print "\t\t\t <a xhref=\"$links[$i]\">$names[$i]</a> \n<br>\n";
}
}
?>


Như bạn thấy trong thí dụ trên, tách biệt khách chủ làm tăng tính dễ đọc trong chương trình của bạn. Một lợi ích khác là một khi bạn đã có các hàm API hiển thị nội dung, bạn có thể để cho thiết kế viên tham gia thay đổi bố cục mà không cần sửa mã chương trình.

1.1. Lợi ích của hàm API
  • Tương đối sáng sủa
  • Nhanh, hầu như không lãng phí thời gian (overhead)
1.2. Bất lợi
  • Không sáng sủa và dễ dàng bằng hệ thống mẫu (template system)
  • Cần một ít kiến thức PHP để sửa mẫu
2. Hệ thống khuôn mẫu

Một cách khác để tách biệt khách chủ là dùng hệ thống khuôn mẫu. Nghĩa là, có một số đánh dấu nội dung sau đó dùng chương trình phân tích, thay thế các đánh dấu đó bằng thông tin cần thiết.

Thí dụ, bạn có thể tạo một tập tin như thế này:

HTML
<html>
<head>
<title>%%PAGE_TITLE%%</title>
</head>
<body %%BODY_PROPERTIES%%>
<h1>%%PAGE_TITLE%%</h1>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td width="25%">%%PAGE_LINKS%%</td>
<td>%%PAGE_CONTENT%%</td>
</tr>
</table>
</body>
</html>


Sau đó có thể viết chương trình phân tách tập tin, thay thế các thông tin trong dấu cách %% bằng các thông tin thích hợp.

Ghi chú: một lớp hỗ trợ hệ thống khuôn mẫu khá tốt là lớp FastTemplate, có ở http://pip.vn

2.1. Ưu điểm của hệ thống khuôn mẫu
  • Rất trong sáng
  • Không cần kiến thức PHP để sửa khuôn mẫu
2.2. Nhược điểm
  • Chậm hơn, bạn cần phân tách tập tin khuôn mẫu, sau đó xuất ra
  • Việc hiện thực phức tạp hơn

7. Dùng các cấu trúc lỗi thời

Có nhiều người cứ dùng mãi các mã và thư viện lỗi thời. Thí dụ như họ đã viết một hàm dùng ở PHP 2, và vẫn còn dùng nó ở PHP 4, mặc dù một hàm có cùng mục đích như thế đã được thêm vào ở PHP 3

Dùng các cấu trúc lỗi thời có thể làm chậm chương trình của bạn, cũng như làm cho nó trở nên khó hiểu. Người đọc các chương trình của bạn có thể không quen với các hàm lỗi thời của PHP. Tuy nhiên, khi phát hiện một đoạn mã lạc hậu, bạn đừng nghĩ rằng cần phải thay thế nó. Chỉ cần chắc chắn rằng bạn sẽ không dùng nó cho các chương trình viết trong tương lai.

Một thí dụ về cấu trúc lỗi thời, mà nhiều người có vẻ cố nắm lấy, là cú pháp beginControlStructure .. endControlStructure;

Mã lệnh (PHP)

<?
// Bad/Outdated Practice
while (1):
print "5";
if ($idx++ == 5):
break;
endif;
endwhile;

// Better Practice
// (the code could be optimized though)
while (1)
{
print "5";
if ($idx++ == 5) {
break;
}
}
?>


Đây là một thói quen xấu vì
  • Nó không được dùng rộng rãi, cho nên nhiều người học sẽ bị lẫn lộn giữa hai cú pháp
  • Nó không tương thích với ngôn ngữ khác, nghĩa là nó trở nên khó đọc đối với những người trong giai đoạn quá độ (mới chuyển từ một ngôn ngữ nào đó sang PHP)
  • Quan trọng nhất, là một ngày nào đó tính năng này sẽ bị xoá xổ, bắt buộc bạn phải viết lại toàn bộ mã có dùng nó. Dấu ngoặc nhọn luôn luôn là một phần của ngôn ngữ PHP.
Ở trên chỉ là một thí dụ về cấu trúc lỗi thời. Nó còn nhiều nữa. Như một quy tắc, bạn nên theo những các viết trong tài liệu PHP. Hầu hết nó được cập nhật mới. Nó cũng dùng các hàm mới nhất của PHP trong thí dụ của mình. Nên thường xuyên kiểm tra tài liệu khi bạn có ý muốn mở rộng tính năng nào đó của PHP. Theo cách này, bạn sẽ không phải viết lại các hàm có sẵn.

Tổng kết
Trong bài này bạn đã đi qua 7 trên tổng số 21 lỗi mà lập trình viên PHP mắc phải. Những lỗi giáo khoa này bao gồm:
  • Sử dụng sai hàm printf()
  • Áp dụng sai ngữ nghĩa
  • Thiếu tài liệu trong mã nguồn
  • Dùng quá nhiều biến tạm
  • Viết lại các hàm có sẵn
  • Không tách biệt phần khách/chủ
  • Dùng các cấu trúc lỗi thời

(còn tiếp)

Tutorials Collection - http://tutorials.pip.vn


Gửi bởi: rain1007ht
Lượt xem: 5663

 
knight 14/11/09 08:46 PM
đang đợi phần 2 đây :)

Quảng cáo Hải Phòng
Quảng cáo Hải Phòng
NHẬN XÉT CỦA BẠN
Trung bình: 2.7   
Kiểu gõ: Off Telex VNI VIQR Tổng hợp (Telex, Vni, Viqr)
Họ Tên:
Hòm thư:
Nội Dung:
Số ký tự còn lại: 3500