hn4u @ Last updated 21/11/04 22:42
Go to my homepage at http://4u.jcisio.com
Full version available at http://4u.jcisio.com/r/article614.htm

Hải Nam

Phần 2: 7 lỗi nghiêm trọng

14. Không tuân thủ các quy ước đặt tên

Một trong những lỗi nghiêm trọng mà người lập trình có thể phạm phải là định nghĩa một quy ước đặt tên tồi. Tôi đã tiếp quản nhiều dự án mà trong đó tôi phải bỏ ra rất nhiều thời giờ chỉ để hiểu chương trình, do lập trình viên đặt tên các biến là $fred và $barney thay cho $email và $name. Tôi đang đề cập đến một dự án mà người lập trình cũ đã quyết định đưa vào toàn bộ chương trình một kiểu đặt tên kì lạ (a Flinstones naming theme), không phải tôi đùa đâu.

Cách bạn đặt tên biến và hàm là trung tâm của việc xây dựng một chương trình dễ đọc. Có nhiều lập trình viên phạm lỗi khi đặt tên biến và hàm mà nó:

Đặt tên biến

Cách viết phân biệt

Trong PHP, tên biến có cách viết phân biệt, nghĩa là $user và $User là hoàn toàn khác nhau. Vài người dùng lợi dụng điểm này để đặt các biên cùng tên nhưng khác cách viết. Đây là một thói quen tồi tệ. Cách viết không bao giờ nên dùng để phân biệt các biến khác nhau. Mỗi tên biến, trong cùng tầm vực (scope), nên có là tuyệt đối duy nhất.

Tên quá ngắn

Nhiều người sử dụng những chữ viết tắt đầu (cryptic acronym) bí ẩn cho các biến của họ, để rồi sau này hối tiếc vì quên mất họ đã muốn ám chỉ điều gì khi đó. Tên biến nên mô tả nội dung nó (sẽ) chứa, dùng nguyên từ hoặc những chữ viết tắt có thể hiểu được.

Tên quá dài

Ở khía cạnh khác, vài người lại sử dụng tên biến quá dài. Nói chung, tên biến không nên dài quá hai từ. Hai từ có thể được tách biệt bằng dấu phân cách "_" hoặc là viết hoa chữ đầu của từ thứ hai.

Thói quen tốt

Dưới đây là những thí dụ tốt về tên biến

Mã lệnh (PHP)
$username  = 'sterling';
$password  = 'secret';

$teachers = array ('Sadlon',
                   
'Lane',
                   
'Patterson',
                   
'Perry',
                   
'Sandler',
                   
'Mendick',
                   
'Zung');

foreach (
$teachers as $teacher);

Thói quen xấu

Dưới đây là những thí dụ (phóng đại) về những tên biến tồi

Mã lệnh (PHP)
$username_for_database = 'sterling';
$guMbi = 'secret'; // for the $password

$thelastnamesofteachers = array ('Sadlon',
                                 
'Lane',
                                 
'Patterson',
                                 
'Perry',
                                 
'Sandler',
                                 
'Mendick',
                                 
'Zung');

foreach (
$thelastnamesofteachers as $TeaChER);

Đặt tên hàm

Mọi khái niệm áp dụng cho tên biến cũng áp dụng cho đặt tên hàm. Tuy nhiên, ngữ pháp đóng vai trò đặc biệt trong các hàm.

Chú ý rằng các hàm PHP, định nghĩa sẵn hoặc do người dùng định nghĩa, là không-phân-biệt-cách-viết (not case sensitive)

Dùng động từ

Hàm của PHP tương đương với một động từ khi nói. Tên hàm, do đó, nên được hướng hành động (action oriented). Nó cũng nên được dùng ở thì hiện tại.

Thí dụ, bạn có một hàm tạo một số ngẫu nhiên với phân bố Gausse (a gaussian random number), bạn nên đặt tên nó là generate_gaussian_rand().

Chú ý các sử dụng động từ hành động trong tên hàm. Nó sẽ đặt hàm vào ngữ cảnh thích hợp

Mã lệnh (PHP)
<?php
list ($num1, $num2) = generate_gaussian_rand();
list (
$num3, $num4) = generate_gaussian_rand();
?>

Để so sánh, hãy xem thí dụ:

Mã lệnh (PHP)
<?php
list ($num1, $num2) = gaussian_rand_generator();
list (
$num1, $num2) = gaussian_rand_generator();
?>

Bạn có thấy sự khác biệt? Thí dụ thứ hai sử dụng danh từ, mặc dù vẫn chuyển tải được mục tiêu của hàm, nhưng nó ngăn người ta đọc một cách trôi chảy.

Hãy sử dụng động từ!

13. Không suy nghĩ thấu đáo: CSDL & SQL

Số cách người ta truy cập cơ sở dữ liệu (CSDL - database) và lấy kết quả nhiều đến mức thực sự ngạc nhiên. Những thí dụ tôi đã gặp bao gồm những tổ hợp lệnh if và vòng lặp do.. while, các câu gọi nhiều lần, và các hàm sql_result() trong vòng for.

Những người này có nghĩ họ đang làm gì không?

Việc viết các mã trật-hoặc-trúng (hit-or-miss code) chứng minh sự thiếu tập trung. Những cá nhân đó xác định nỗ lực của họ dùng để hoàn thành công việc hơn là để hoàn thành đúng công việc, kết quả là làm cho các ông chủ quăng thời gian và tiền bạc ra đường.

Sự lấy mẫu không chính xác là một thí dụ hay về vấn đề này. Vài người viết lệnh không dành thời gian để nghĩ thấu đáo. Đúng là không chỉ có duy nhất một cách “đúng” để lấy mẫu dữ liệu, nhưng nó có rất nhiều cách không đúng.

Phần này bao gồm các chủ đề:

Dùng sai các hàm CSDL

Một đoạn mã PHP đã dùng cú pháp sau để lấy kết quả từ CSDL (presented below using a generalized set of SQL functions):

Mã lệnh (PHP)
if (!($row = sql_fetch_row ($result))) {
    print
"An error occurred: no rows found";
    exit;
}

do {
    print
"$row[0]: $row[1]\n<br>\n";
} while (
$row = sql_fetch_row ($result));

Chú ý: Ở trên, và các thí dụ sau nữa, $result diễn tả handle hoặc pointer đến một tập kết quả truy vấn.. Nói cách khác, một truy vấn đã được gửi và một tập kết quả đã được trả về. Các thí dụ sẽ nói về vấn đề thao với với kết quả trả về.

Có một vài vấn đề với đoạn mã trên:

Kiểm tra trường hợp không tìm thấy: cách làm sai

Bằng cách dùng sql_fetch_row(), PHP chủ trương một cách tiếp cận hàm ẩn cho việc xác định có kết quả tìm thấy hay không. Một cách khác trực tiếp và tường minh là đếm số dòng của kết quả bằng sql_num_rows() như dưới đây:

Mã lệnh (PHP)
<?php
if (sql_num_rows ($result) <= 0) {
    print
"An error occurred: no rows found";
    exit;
}

while (
$row = sql_fetch_row ($result)){
    print
"$row[0]: $row[1]\n<br>\n";
}
?>

Từ bỏ vòng lặp Do..While

Trước hết và trên hết, vòng lặp thô tục do..while không bao giờ cần nữa vì khi dùng sql_num_row(), chúng ta không phải lấy dòng đầu tiên của kết quả khi muốn kiểm tra kết quả trống.

Thí dụ cũ đã diễn tả một thí dụ mà trong đó, nếu kết quả không rỗng, dòng đầu tiên đã được lấy bằng hàm sql_fetch_row() trong câu lệnh if. Cấu trúc do..while cần thiết trong trường hợp này vì khi đó, bộ đếm của CSDL đã tăng lên và chuyển sang dòng kế tiếp. Do đó, bạn phải xử lí (lệnh do)dòng đầu tiên vì nó đã được lấy. Các lệnh tiếp theo lấy các dòng kế, và cứ thế.

Tại sao vòng do..while bị coi như thô tục, xấu xa (nasty)?

Giữ mọi thứ gọn gàng và đơn giản

Với trường hợp kết quả rỗng, sql_num_rows()đưa đến ngay kết quả, trong khi sql_fetch_row() thì không

Nhưng điều đó thực sự tạo nên sự khác biệt nào?

Xét cùng một sự so sánh, nhưng bây giờ là trong ngữ cảnh của điều kiện if và của biểu thức, trong đoạn lệnh giả (Pseudo-code):

Biểu thức nào dể hiểu hơn? Rõ ràng là cách đếm sẽ trực tiếp và gọn gàng hơn.

Sự khác biệt thực tế là gì? Với một lệnh if đơn giản, chúng ta không thu được lợi nhều.

Tuy nhiên, với hơn 10 000 dòng lệnh, hãy dành thời gian nghĩ đến cách rõ ràng nhất, nó sẽ tiết kiệm cho người phân tích chương trình nhiều giờ suy nghĩa. Lợi ích khác có thể kể đến là chương trình của bạn sẽ nhanh hơn và dễ phát triển hơn.

Khi mà DBMS của bạn không hỗ trợ sql_num_row()

Vài DBMS có thể không hỗ trợ hàm sql_num_row(). Tôi xin chia sẻ với bạn nếu DBMS của bạn là một trong số đó. Bạn sẽ phải tìm trong kết quả rỗng bằng cách lấy dòng. Tuy nhiên, trong trường hợp này, nó nên dùng một biến boolean như sau:

Mã lệnh (PHP)
<?php
$found = false;

while (
$row = sql_fetch_array($result)){
  
$found = true;
}

if (!
$found){
  print
"Error";
}
?>

Lấy kết quả: hãy chọn cách có ích

Vấn đề thứ hai trong đoạn mã này là nó dùng sql_fetch_row() để lấy tập kết quả. Hàm sql_fetch_row() chỉ trả về mảng đánh chỉ số, trong khi đó sql_fetch_array() trả về mảng đánh chỉ số mảng dùng chuỗi.

Mã lệnh (PHP)
$row = sql_fetch_array ($result);
print
$row[1]; // Second column
print $row[name]; // The name column

Chú ý: Có nhiều quy ước khác nhau về việc dùng dấu nháy khi thêm một đối số kiểu chuỗi. Trong thí dụ về tên cột ở trên, và suốt bài viết này, nó sẽ được bỏ.

Từ quan điểm của nhà phát triển, hàm nào có lợi hơn? Mảng dùng chuỗi giúp cho người đọc hiểu được bạn đang lấy cái gì chỉ thông qua việc đọc mã, như thí dụ đúng dưới đây:

Mã lệnh (PHP)
<?php
if (sql_num_rows ($result) <= 0) {
      print
"An error occurred: no rows found";
      exit;
}

while (
$row = sql_fetch_array ($result)) {
  print
"$row[name]: $row[phone_number]\n<br>\n";
}
?>

Khi nào sql_fetch_row($result) nên được dùng

Tôi không thực sự là fan của the sql_fetch_row(). Tuy nhiên, có một tình huống mà dùng nó không giảm khả năng dễ đọc: khi người dùng định nghĩa câu truy vấn.

Các thí dụ cho đến lúc này đều đề cập đến những câu truy vấn được biết trước. Đôi khi bạn để cho người dùng tự định nghĩa câu truy vấn. Trường hợp này bạn sẽ không biết các cột trong kết quả.

Do đó, dùng hàm sql_fetch_row() kèm với count() sẽ xử lí hiệu quả các cột trong một hàng:

Mã lệnh (PHP)
<?php
for ($i = 0; $i < count($row); $i++){
  print
"Column". ($i + 1). $row[$i]. "\n<BR>\n";
}
?>

Dùng sai SQL: không lấy những gì bạn cần

Như là vấn đề của thực hành, đơn giản là sẽ sai lầm khi dùng PHP xử lí mọi dòng của CSDL. Tôi đã bắt gặp người ta dùng PHP để chạy một chương trình tìm kiếm đơn giản trên 2MB dữ liệu và tự hỏi tại sao cái ngôn ngữ này chạy lâu thế. Lấy 2MB dữ liệu từ CSDL có thể làm bạn chờ mãi mãi.

Ngôn ngữ truy vấn chuẩn (Standard Query Language - SQL) được thiết kế đặc biệt để truy vấn và lấy dữ liệu từ các bảng của bạn. Ý tưởng là dùng nó để lọc dữ liệu không cần thiết, để lại các thông tin liên quan cho PHP xử lí.

Nếu bạn lấy nhiều dữ liệu hơn cần thiết, đó là dấu hiệu chắc chắn rằng mã SQL đang dùng chưa được tối ưu hoá.

Mệnh đề WHERE

Một thí dụ kinh điển về sự hiểu quả của SQL liên quan đến mệnh đề where.

Đoạn mã sau sẽ lấy các kết quả và in ra tên và số điện thoại của người có id là 5:

Mã lệnh (PHP)
<?php
//
// The connection is established and $conn is
// defined as the connection handle before this
// code.

$statement = "SELECT name, phone, id FROM samp_table";
$result = @sql_query ($statement, $conn);

if (!
$result) {
      die (
sprintf ("Error [%d]: %s",
             
sql_errno (), sql_error ()));
}

if (@
sql_num_rows ($result) <= 0) {
      die (
"No results retrieved from Database");
}

while (
$row = @sql_fetch_array ($result)){
      if (
$row[id] & 5) {
          print
"Name: $row[name]\n<br>\n";
          print
"Phone: $row[phone]\n<br>\n";
          break;
      }
}
?>

Đoạn mã trên chưa được tối ưu: chúng ta đang dùng PHP để tìm kiếm trong toàn bộ CSDL! Nếu như điều này không quan trọng đối với các CSDL nhỏ, khi kích thước CSDL tăng lên bạn sẽ cảm thấy một cú đấm nặng nề về hiệu năng.

Lời giải rất đơn giản: sửa câu SQL để chứa mệnh đề WHERE:

Mã lệnh (PHP)
$statement = "SELECT name, phone FROM samp_table";
$statement .= " WHERE id='5'";

Mệnh đề WHERE cho phép bạn tìm kiếm chọn lọc hơn. Giới hạn chọn lọc của mệnh đề where chính là một hàm với đối số của nó. Trong thí dụ trên đối số là "id=5".

Bây giờ chúng ta đã chọn được dữ liệu cần thiết, bạn chỉ việc dùng PHP để in ra sau đó:

Mã lệnh (PHP)
if (@sql_num_rows ($result) != 1) {
      die (
"Incorrect number of results retrieved from DB");
}

$row = @sql_fetch_array ($result);
print
"Name: $row[name]\n<br>\n";
print
"Phone Number: $row[phone]\n<br>\n";

Dùng PHP sắp xếp kết quả

Nhiều người lấy dữ liệu ở tình trạng không có thứ tự, nhưng rồi đoạn mã PHP tiếp theo lại sắp thứ tự chúng. Nên chớ rằng sắp xếp bằng SQL nhanh hơn PHP.

Dùng cú pháp ORDER BY của SQL để sắp xếp thay vì hàm ksort() của PHP.

Thí dụ dưới đây dùng ksort() để sắp xếp theo tên:

Mã lệnh (PHP)
$statement = "SELECT name, email, phone FROM some_table ";
$statement .= "WHERE name IS LIKE '%baggins'";

$result = @sql_db_query ($statement, "samp_db", $conn);

if (!
$result) {
  die (
sprintf ("Error [%d]: %s",
              
sql_errno (),sql_error ()));
}

while (
$row = @sql_fetch_array ($result)){
   
$matches[ $row[name] ] = array ($row[email],
                                      
$row[phone]);
}

ksort ($matches);

Nhưng tại sao không sắp xếp dữ liệu ngay vào lúc nó được định nghĩa? Nó giúp chúng ta đỡ phải duyệt qua tập kết quả lần thứ hai.

Do vậy, bỏ hàm ksort() ra khỏi chương trình trên và thay đoạn mã SQL bằng đoạn dưới đây, có dùng dùng cú pháp ORDER BY:

Mã lệnh (PHP)
$statement = "SELECT name, email, phone FROM some_table ";
$statement .= "WHERE name IS LIKE '%baggins' ORDER BY name";

12. Thiếu sự kiểm lỗi

Tôi đã thấy nhiều chương trình thiếu một lượng kiểm tra lỗi đầy đủ. Nguyên nhân phần lớn là do lập trình viên không dành thời gian để lên một kế hoạch thích hợp cho chương trình của mình, và xác định những vị trí có thể dẫn đến lỗi. Kiểm tra lỗi không nên thực hiện sau khi viết chương trình. Sự thiếu sót trong tầm nhìn trước có thể dẫn đến những lỗi nghiêm trọng, không những gây ra kết quả sai mà thậm chí còn làm hỏng hệ thống (even cause your system to crash)!

Mong đợi điều tệ nhất

Mọi chương trình đều có khả năng hư hỏng trong những tình huống sai. Để giảm thiểu những rủi ro như thế, bạn cần lên kế hoạch để:

Kiểm tra kết quả lời gọi hàm

Mỗi khi bạn gọi một hàm làm thay đổi nhiều dữ liệu, luôn kiểm tra để đảm bảo rằng kết quả trả về trong phạm vi giá trị được chấp nhận (a range of allowable values).

Trong thí dụ dưới đây, một lỗi illegal division by zero sinh ra trong lần lặp thứ 6 của vòng for ($i được tăng lên 1 trong khi $j bị giảm đi 1). Vào lần thứ 6, khi đó $i = $j = 0.

Mã lệnh (PHP)
<?php
mt_srand((double)microtime() * 10000000);

function
do_math ($a, $b)
{
    return ((
$a - $b) * 2) / mt_rand();
}

for (
$i = 5, $j = -5; $i > -5; $i--, $j++){
    print
$j / do_math ($i, $j) . "\n";
}
?>

Kiểm tra kết quả lời gọi hệ thống

Luôn đảm bảo rằng, khi bạn làm việc với các tiến trình hoặc tập tin ngoài PHP, mọi thứ đều vận hành đúng.

Một thí dụ tuyệt vời là việc kiểm tra đầu ra của một lời gọi hệ thống khi dùng hàm sql_connect(). Xác nhận đầu ra để kiểm tra liên kết đến CSDL là đúng. Làm sai điều này có thể dẫn đến các truy vấn hỏng và mất dữ liệu trong khi thậm chí bạn không biết.

Mã lệnh (PHP)
$conn = @sql_connect ($host, $user, $pass);

if (!
$conn) {
    die (
sprintf ("Error [%d]: %s",
             
sql_errno (), sql_error ()));
}

Đặt  mức error_reporting là E_ALL trong tập tin php.ini

Hãy đảm bảo bạn cấu hình với mức độ báo lỗi cao nhất có thể. Nếu bạn không đặt nó ở mức cao nhất, ít nhất là trong quá trình tìm lỗi (debugging), bạn có thể bỏ qua những lỗi như là biểu thức chính quy (regular expressions) không hợp lệ và các giá trị không chính xác.

Xem lại lần nữa thí dụ tôi đã đưa trong phần Kiểm tra kết quả lời gọi hàm, ở dưới đây. Giả sử bạn đặt error reporting ở mức thấp,E_ERROR.

Chú ý rằng kết quả in ra khi chương trình thi hành hàm do_math: không có thông báo  illegal division by zero đã từng hiện ra lần trước, phần $i=$j=0 đơn thuần không hiện kết quả.

Mã lệnh (PHP)
<?php
error_reporting (E_ERROR);

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

function
do_math ($a, $b)
{
    return ((
$a - $b) * 2) / mt_rand();
}

for (
$i = 5, $j = -5; $i > -5; $i--, $j++){
    print
$j / do_math ($i, $j) . "\n";
}
?>

Kết quả hiện ra

-5148.25

-5271

-323.75

-4931

-7713.5

-4702.5

-488.5

-928.5

-1394.75

Bộ quản lí lỗi tuỳ chỉnh

PHP thường hiển thị các lỗi thực thi (execution errors) ra trình duyệt, ngăn bạn xoá (suppress) hoặc bắt (capture) nó. Tuy nhiên, với PHP4 bạn đã có thể bắt lỗi bằng hàm set_error_handler().

Hàm set_error_handler() có thể được dùng để ghi lại các lỗi xảy ra với chương trình của bạn. Thay vì làm phiền người dùng với các thông báo lỗi, bạn có thể ghi lại cho riêng bạn, bằng cách đặt một hàm quản lí lỗi tuỳ chỉnh (a custom error handling function).

Trong thí dụ dưới, set_error_handler() được dùng để chỉ định hàm error_handler() là bộ quản lí lỗi mặc định.

Khi một lỗi xảy ra, error_handler() được gọi và hàm PHP error_log() được dùng để ghi lỗi vào tập tin error_file.

Nếu mà lỗi thuộc loại E_ERROR, chúng ta sẽ thoát chương trình và in thông báo lỗi.

Mã lệnh (PHP)
<?php
// void error_handler(string type, string message, string file, int line)
//  Custom error handler, set by the set_error_handler()
//  function.
//

function error_handler ($type,
                       
$message,
                       
$file=__FILE__,
                       
$line=__LINE__)
{
  
error_log("$message, $file, $line", 3, 'error_file');

  if (
$type & E_ERROR) {
       print
'An error occurred, it has been logged
            and it will be addressed.'
;
      exit;
  }
}

set_error_handler('error_handler');
?>

11. Lạm dụng Hướng đối tượng (HĐT)

Mô hình hướng đối tượng là một khái niệm tuyệt vời. Nó có rất nhiều lợi điểm, mà đáng chú ý nhất là khả năng dùng lại mã dễ dàng. Tuy nhiê, theo như chúng ta được hiểu: PHP không phải là một ngôn ngữ HĐT.

Mặc dù PHP có một sự hỗ trợ đầy đủ về HĐT, nó không hiệu quả lẫn không khôn ngoan nếu dùng tính năng HĐT của nó khi bạn có các hàm khác để đạt được cùng kết quả. Lí do là sự hỗ trợ HĐT của PHP không được phát triển mạnh.

Trong khi có hầu hết các phần tử chính yếu, PHP vẫn còn thiếu vài tính năng cao cấp (như các khái niệm protected, private) mà một ngôn ngữ HĐT thực sự (thí dụ như C++ , Java) phải có.

Các mã hỗ trợ HĐT của PHP không được tinh chỉnh và cũng không hiệu quả. Nghĩa là nếu bạn dùng mô hình HĐT trong PHP, bạn có thể làm chậm chương trình đáng kể.

Nói chung, một ứng dụng dùng HĐT sẽ chậm đi, cũng như là bạn dùng eval() thì sẽ chậm hơn là dùng mã bình thường. Để minh hoạ đầy đủ hơn việc HĐT có gì đó không tốt, tôi đã từng phải dùng những tính năng và khái niệm cao cấp của PHP, một vài trong số đó thậm chí chưa có tài liệu chỉ dẫn.

Chúng ta có thể làm gì mà không cần HĐT?

Nếu bạn chuyển sang PHP từ các ngôn ngữ như Java hay C++ (nơi bạn bạn thực sự không thể tạo các chương trình phức tạp mà không dùng các tính năng HĐT), việc bỏ qua khả năng HĐT của PHP có thể sẽ khó khăn. Dù sao, tôi vẫn có thể trấn an bạn là các chương trình rất mạnh có thể được viết mà không dùng mất cứ khái niệm và mô hình HĐT nào (PHP được viết bằng C, ngôn ngữ không hỗ trợ HĐT).

Để dành cho những ai không quen với kĩ năng phi-HĐT, dưới đây là vài kĩ thuật để tạo chương trình có tính kết dính và dễ mở rộng mà không dùng mô hình HĐT:

Tạo một API

Áp dụng 3 lớp cho chương trình của bạn:

MortgageRate.php

Mã lệnh (PHP)
<?php
// The internal functions are layer 1

// Internal function to calculate the correct
// interest rate to be used given the amount per month
// and the time it is to be paid in

function _mort_find_interest_rate ($total)

{
        if (
$total < 30000)
            return (
7.4);
        elseif (
$total > 30000)
            return (
3.2);
        elseif (
$total > 50000)
            return (
2.5);
        else
            return (
1.7);
}


// The API is layer 2

// double calculate_mortgage_rate (int money, int time, int month)
//   Calculate the mortgage rate given the
//   the total money, time its paid over and
//   the intervals

function calculate_mortgage_rate ($money, $time, $month)

{
   
$rate       = _mort_find_interest_rate ($money) / 100;
   
$money     /= ($time / $month);
    return (
$rate * $money) + $money;
}
?>

CalcMortgage.php

Mã lệnh (PHP)
<?php
// The actual application is layer 3

// $money, $time and $period are submitted
// from a form

include_once 'MortgageRate.php';

$price = calculate_mortgage_rate ($money, $time, $period);

print
"Your $period month cost is $price";
?>

Tạo một trình tự tên và luôn tuân thủ

Một trong những vấn đề chính trong bất cứ một dự án lớn nào là sự xung đột về tên. Các lớp có thể phân đoạn tên. Do đó, các lớp khác nhau có thể:

Thí dụ, lớp Phillips và lớp Normal có thể cùng có phương thức tên screwdriver.

Nói chung, trước khi bắt đầu một dự án lớn nào, bạn nên có một trình tự tên cho mọi thứ, cụ thể là cách bạn tách các biến toàn cục ra các biến thông thường, cách định nghĩa hàm trong thư viện v.v.

Nhóm các ý niệm chung vào một tập tin

Nhóm các hàm API tương tự vào chung một tập tin cũng giống như nhóm các phương thức tương tự vào một lớp. Cố gắng tưởng tượng mỗi tập tin bạn tạo là một lớp, mỗi hàm trong đó là một phương thức. Bằng cách này, các hàm của bạn sẽ có định nghĩa và cấu trúc sáng sủa.

Thí dụ, bạn có thể muốn nhóm mọi hàm liên quan đến truy cập CSDL vào một tập tin DB.php.

HĐT, giống mọi thứ, tốt khi có điều độ

Để tôi làm sáng tỏ một việc. Tôi không phải đang cố biện hộ để bạn từ bỏ hẳn HĐT trong PHP. Đúng ra, tôi chỉ đang cố cảnh báo bạn đừng nên dùng PHP như Java hay C++, nơi mà HĐT có thể dùng thoải mái.

Hãy cẩn thận đánh giá lợi và hại trước khi bạn dùng một tiếp cận HĐT với PHP.

10. Dùng nhầm Biểu thức Chính quy

Biểu thức chính quy (Regular expressions) là công cụ mạnh để tìm và tổ chức dữ liệu, như là kiểm định địa chỉ e-mail hoặc kiểm tra một URL. Tuy nhiên, nó chậm hơn các công cụ của PHP trong một số tác vụ đơn giản.

Thí dụ, nếu bạn muốn viết hoa toàn bộ môt chuỗi, một lính mới của PHP có thể làm như sau:

Mã lệnh (PHP)
<?
$URL = "http://www.php.net";

$fp = @fopen ($URL, "r");
if (!
$fp) {
die (
"Cannot open website $URL!");
}

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

@
fclose ($fp)
or
warn ("Cannot close website handle, $URL");

$data = ereg_replace ("[a-z]", "[A-Z]", $data);

print
$data;
?>

Tuy nhiên, sẽ phí thời gian khi bạn dùng hàm ereg_replace() (chậm hơn) để làm công việc mà strtoupper() (nhanh hơn) có thể thực hiện tốt hơn.

Mã lệnh (PHP)
$data = strtoupper ($data);

Nói chung, bạn luôn cố gắng dùng các thay thế đơn giản của biểu thức chính quy vì nó sẽ làm tăng khá nhiều tốc độ chương trình.

Các hàm cần biết

Có vài hàm rất thiết yếu để tiết kiệm thời gian thi hành chương trình khi dùng thay cho biểu thức chính quy. Dưới đây là danh sách các hàm thiết yếu đó:

strtoupper(); strtolower(); ucfirst(); strtr(); str_replace(); trim(); explode(); implode(); substr(); strcmp()

Nếu bạn thay thế các biểu thức chính quy của bạn bằng các hàm trên, bạn có thể trông đợi một sự nhảy vọt về hiệu năng, đặc biệt khi bạn làm việc với các chuỗi lớn.

9. Lập trình PHP như các ngôn ngữ khác

Nhiều người bắt đầu PHP sau khi đã thuần thục một ngôn ngữ khác như Perl, C, Java hay ASP . Làm như vậy, họ cũng mang theo những mô hình mà có thể không luôn luôn được dùng bởi PHP.

Không may thay, vài cá nhân trong số hày không chịu bỏ thời gian để học cách lập trình PHP theo cách phù hợp với PHP. Thay vào đó, họ thích PHP hoạt động với các ít khái niệm mới càng tốt

Khi bạn lập trình PHP như là trong các ngôn ngữ khác, nó thường dẫn đến việc làm chương trình chậm hơn và khó bảo trì mã. Bạn sẽ thường thấy họ phạm vào một trong các lỗi sau:

Perl "1 dòng"

PHP là một ngôn ngữ không thực sự tối ưu cho cách tiếp cận 1 dòng khi viết chương trình. Thay vào đó, nó mở rộng các tập hàm phức tạp và các biểu thức chính quy theo một định dạng có cấp bậc hơn

Perl

Mã lệnh (Perl)
while (<STDIN>) {
   @_ = split /:/;
   $quotes{shift} = shift;
}

print map { "$_: ", reverse split //,$quotes->{$_},"\n";
           } keys %quotes;

PHP

Mã lệnh (PHP)
<?php
$fp = @fopen('php://stdin', 'r');
if (!
$fp) {
    die (
'Cannot open STDIN');
}

while (
$line = @fgets ($fp, 1024)){
  list(
$name, $quote) = explode (':', $line);
  
$quotes[ $name ] = $quote;
}

foreach (
$quotes as $name => $quote){
    print
"$name: ";
    print
implode (" ", array_reverse (preg_split ('//',
$quote)));
    print
"\n";
}

@
fclose ($fp);
?>

Không dùng các hàm sẵn có

Nhiều lập trình viên PHP có nền tảng C có vẻ không nhận ra rằng PHP cung cấp nhiều hàm sẵn có giúp thay thế các đoạn mã dài. Nếu bạn đến với PHP từ C, tôi khuyên bạn nên đọc qua tài liệu trước khi viết một khối lệnh để xem PHP có sẵn hàm nào giúp cuộc sống của bạn dễ dàng hơn không.

Đổi tên các hàm PHP đã có

Tôi đã thấy người ta đổi tên các hàm đã có của PHP chỉ để giúp họ dễ nhớ hơn. Điều này không chỉ làm chậm chương trình, mà làm cho đoạn mã khó đọc hơn.

Dùng hướng đối tượng quá mức

PHP không phải là một ngôn ngữ HĐT, dù nó cung cấp các tính năng HĐT. Bạn cần luôn nhận thức rằng HĐT trong PHP sẽ làm chậm đáng kể chương trình.

Lấy thông tin ở đâu?

May mắn là có rất nhiều thông tin về việc làm thế nào để lập trình PHP. Vài nơi tốt nhất là

8. Không nhận thức đầy đủ về bảo mật

Những người dùng không phải lúc nào cũng làm việc với hệ thống của chúng ta. Với tư cách là lập trình viên, trách nhiệm của chúng ta là thiết kế một hệ thống an toàn, dễ chịu có thể làm việc được chung với lỗi của người dùng.

Khi thiết kế hệ thống, bạn phải đặt mình vào vị trí người dùng. Xem xét những chỗ họ có thể gặp lỗi và tìm kiếm những lỗ hổng bảo mật tiềm tàng. Rồi bạn thiết kế chương trình có khả năng sửa chữa lỗi này và lấp các lỗ hổng bảo mật. Một điều khác cũng quan trọng là: dù xảy ra hư hỏng hay hệ thống bị tấn công là do lỗi của người dùng, chính bạn là người chịu trách nhiệm nếu bạn đã viết chương trình có nhiều lỗi hay thiếu những bước kiểm tra cần thiết dẫn đến hỏng dữ liệu.

Thí dụ, tôi đã thấy nhiều chương trình không dùng hàm đã có của PHP mail() vốn an toàn mà lại đi dùng sendmail thông qua  popen(). Nó có thể dẫn đến nhiều lỗ hổng bảo mật (thí dụ như /etc/passwd được gửi đến người dùng cuối).

Có vài nơi thường xảy ra các sự cố về bảo mật, hoặc tiềm năng gây ra hỏng dữ liệu rất lớn:

Bảo mật của lời gọi hệ thống

Mỗi khi bạn đưa dữ liệu của người dùng vào lời gọi hệ thống, bạn cần tỉnh táo kiểm tra dữ liệu đó. Đảm bảo rằng không có gì nguy hiểm nằm trong dữ liệu đó có thể lừa phỉnh hệ thống thực hiện những lệnh không mong muốn. PHP cung cấp một hàm làm điều đó: EscapeShellCmd()

Bất cứ khi nào bạn chuyển một lệnh có chứa dữ liệu nhạy cảm, trốn thoát dữ liệu đó bằng hàm EscapeShellCmd():

Trốn thoát (escaping) dữ liệu có nghĩa là thêm dấu sổ ngược (backslash \) trước kí tự có thể lừa phình hệ thống (chính xác là các kí tự #&;'\"|*?~<>^()[]{}$\\\x0A\xFF).

HTML
<html>
<head>
   <title>Name Lookup</title>
</head>
<body>
<h1>Name Lookup</h1>
<?php
if ($name) {
   system (EscapeShellCmd ("lookup $name"));
   print "<br>nn";
}
?>
<form action="<?php print $PHP_SELF; ?>" method="GET">
Enter a name to lookup:
<input type="text" name="name">
<input type="submit" value="Lookup Name">
</form>
</body>
</html>

Dù  EscapeShellCmd() là một hàm tốt để kiểm tra lệnh, bạn vẫn nên thử và thực hiện các kiểm tra đặc thù phụ thuộc vào loại dữ liệu. Hàm EscapeShellCmd() sẽ không kiểm tra tính đúng đắn của dữ liệu được đệ trình, nó sẽ chỉ ngăn cản người dùng làm các việc không được phép.
Đi xa hơn một bước

Như một quy luật, nên kiểm tra các kí tự được phép hơn là kiểm các kí tự không được phép.

Thí dụ, đảm bảo rằng $name chỉ chứa các kí tự chữ và số (alphanumeric characters). Bằng cách này, rất khó để khai thác lỗ hổng trong hệ thống của bạn.

Kiểm tra địa chỉ e-mail

Một trong những hình thức kiểm tra phổ biến nhất là xem một địa chỉ e-mail có hợp lệ không. Mấy tay mới vào nghề sẽ chỉ dùng các biểu thức chính quy (mà họ lượm được trong một nhóm thư tín, hay trong một kho mã nguồn nào đó). Tuy nhiên, một biểu thức chính quy không đủ nếu bạn muốn có kết quả chính xác. Có vài cách an toàn hơn mà bạn có thể dùng:

Kiểm tra kết nối (socket validation)

Một cách để kiểm định địa chỉ e-mail mà không quấy rầy trực tiếp đến người dùng là tạo một kết nối đến server nhận được trong địa chỉ e-mail, sau đó tìm tên đăng kí của họ.

Ưu điểm

Khuyết điểm

Kiểm tra tương tác

Một cách khác để kiểm định địa chỉ e-mail là gửi một khoá đặc biệt đến hộp thư người dùng, và bắt họ nhập khoá đó để tiếp tục. Điều này đảm bảo rằng không những địa chỉ e-mail là hợp lệ, mà người dùng có quyền truy xuất vào địa chỉ đó.

Ưu điểm

Khuyết điểm

Tóm tắt

Trong bài này, chúng ta đã thảo luận về những lỗi loại hai, 7 lỗi nghiêm trong từ #14 đến 8, mà người lập trình PHP mắc phải. Những lỗi nghiêm trọng này là:


hainam4u @ Last updated 21/11/04 22:42
Go to my homepage at http://4u.jcisio.com