Điểm qua một số đoạn code chưa được ổn lắm (Phần 1)
Bài đăng này đã không được cập nhật trong 4 năm
Bài viết dưới đây sẽ chỉ ra một vài trường hợp có thể gặp phải khi viết code PHP nói chung, Laravel nói riêng. Đó có thể là lỗi logic cơ bản, cách sử dụng hàm chưa hợp lý; Hoặc chỉ đơn giản là những lỗi convention không đáng có.
1. Sử dụng try-catch
với thứ tự catch
chưa đúng
try {
...
} catch (\Throwable $exception) {
report($exception);
} catch (ValidationException $exception) {
reportValidationException($exception);
} catch (\Exception $exception) {
reportException($exception);
}
Vì Throwable là class cha của Exception và ValidationException nên ở ví dụ trên, exception được throw sẽ được bắt ngay tại catch đầu tiên và hai đoạn catch phía dưới sẽ trở nên vô nghĩa.
Khi sử dụng try catch, hãy chú ý đến thứ tự và quan hệ cha con của các loại exception nhé.
Đoạn code trên có thể sửa lại như sau:
try {
...
} catch (ValidationException $exception) {
reportValidationException($exception);
} catch (\Exception $exception) {
reportException($exception);
} catch (\Throwable $exception) {
report($exception);
}
2. Xử lý exception giống nhau trong câu lệnh catch
try {
...
} catch (ValidationException $exception) {
report($exception);
} catch (\Exception $exception) {
report($exception);
}
Trong cả hai trường hợp catch
, đều có chung một xử lý là gọi tới hàm report()
. Khi đó việc tách thành hai câu lệnh catch
trở nên không cần thiết.
Nếu chỉ muốn catch
ValidationException thì nên bỏ câu lệnh catch
phía dưới.
Nếu muốn bắt tất cả exception và có xử lý trong catch
giống nhau thì nên bỏ câu lệnh catch
bên trên.
3. Sử dụng cú pháp if
không cần thiết
if ($a == $b) {
return true;
} else {
return false;
}
Hoặc có thể ngắn hơn chút.
return $a == $b ? true : false;
Hai đoạn code trên viết không sai về logic nhưng khá thừa thãi và dài dòng. Đối với những logic so sánh đơn giản, cân nhắc rút ngắn lại như sau:
return $a == $b;
4. Khai báo biến chỉ được sử dụng một lần
function foo() {
$a = 'bar';
return $a;
}
Việc khai báo biến cục bộ $a
trong trường hợp này là không cần thiết.
Đối với các phép toán không quá dài và phức tạp, nên cân nhắc sửa lại như sau:
function foo() {
return 'bar';
}
5. Sử dụng hàm array_splice()
trong vòng lặp của chính mảng hiện tại.
$numbers = [1, 2, 3, 4, 5, 6];
foreach ($numbers as $key => $number) {
if ($number % 2 !== 0) {
array_splice($numbers, $key, 1);
}
}
var_dump($numbers);
Hàm array_splice()
là một trong những giải pháp được nghĩ tới trong các bài toán loại bỏ các phần tử không mong muốn ra khỏi một mảng.
Trong ví dụ trên, hàm array_splice()
được sử dụng để loại bỏ các số lẻ bên trong mảng $numbers
.
Kết quả mong muốn khi chạy đoạn code trên là:
$numbers = [2, 4, 6];
Tuy nhiên, thực tế thì kết quả trả về lại là
$numbers = [2, 3, 5, 6];
Nguyên nhân là hàm array_splice()
đã làm thay đổi mảng hiện tại và gây ra sự sai lệnh khi sử dụng biến chỉ mục $key
. Giá trị của $key
không còn phản ánh đúng vị trí của phần tử cần được loại bỏ.
Có thể kiểm chứng lại bằng cách hiển thị dữ liệu sau mỗi vòng lặp foreach
$numbers = [1, 2, 3, 4, 5, 6];
foreach ($numbers as $key => $number) {
if ($number % 2 !== 0) {
var_dump($numbers);
var_dump('Spliced number: ' . $number);
var_dump('Spliced postion: ' . $key);
array_splice($numbers, $key, 1);
}
}
var_dump($numbers);
array(6) {
[0] => int(1)
[1] => int(2)
[2] => int(3)
[3] => int(4)
[4] => int(5)
[5] => int(6)
}
string(17) "Spliced number: 1"
string(18) "Spliced postion: 0"
array(5) {
[0] => int(2)
[1] => int(3)
[2] => int(4)
[3] => int(5)
[4] => int(6)
}
string(17) "Spliced number: 3"
string(18) "Spliced postion: 2"
array(4) {
[0] => int(2)
[1] => int(3)
[2] => int(5)
[3] => int(6)
}
string(17) "Spliced number: 5"
string(18) "Spliced postion: 4"
Đây là lỗi khá cơ bản nhưng đôi khi lại bị bỏ qua, dẫn đến những kết quả không lường trước.
Với bài toán ở ví dụ trên, có nhiều cách giải quyết khác nhau. Hai trong số nhiều cách có thể cân nhắc tới:
Sử dụng vòng lặp for
và giảm giá trị của biến con trỏ tại thời điểm "splice"
$numbers = [1, 2, 3, 4, 5, 6];
for ($i = 0; $i < count($numbers); $i++) {
if ($numbers[$i] % 2 !== 0) {
array_splice($numbers, $i, 1);
$i--;
}
}
Sử dụng hàm array_filter()
$numbers = [1, 2, 3, 4, 5, 6];
$evenNumbers = array_filter($numbers, function ($number) { return $number % 2 === 0; });
6. Gán giá trị có chứa ký tự space cho biến môi trường của Laravel
APP_NAME=My App
Các biến trong file dotenv (của Laravel) không hỗ trợ các giá trị có ký tự whitespace chưa escape. Do đó, ở ví dụ trên, lỗi sẽ được đổ ra với nội dung như sau (Laravel 5.8):
The environment file is invalid!
Failed to parse dotenv file due to unexpected whitespace. Failed at [My App].
Vì vậy, hãy luôn ghi nhớ escape các giá trị của biến môi trường trong Laravel.
APP_NAME="My App"
7. Truyền giá trị null
vào hàm sử dụng type-hint
function foo(int $a) {
return $a;
}
$b = foo(null);
Đoạn code trên sẽ sinh ra lỗi (PHP Fatal error) khi có sự khác biệt về kiểu dữ liệu giữa tham số truyền vào và đối số của hàm.
Có thể giải quyết vấn đề trên bằng một trong hai phương án sau:
Sử dụng nullable type (Từ PHP 7.1 trở lên)
function foo(?int $a) {
return $a;
}
Gán giá trị mặc định bằng null cho đối số của hàm
function foo(int $a = null) {
return $a;
}
8. Sử dụng loose comparisons (==
, !=
) trên hàm strpos()
function doesContain($str, $char) {
return strpos($str, $char) != false;
}
Hàm strpos()
thường được dùng để xác định một substring có nằm trong string cho trước hay không nhờ vào vị trí của substring đó. Nếu không tìm thấy substring, hàm strpos()
trả về giá trị false
.
Với ví dụ trên, hàm doesContain()
sẽ trả về true
nếu string $str
chứa chuỗi ký tự $char
.
var_dump(doesContain('Hello world!', 'l'));
bool(true)
Tuy nhiên, khi kiểm tra chữ H
hoặc chuỗi ký tự Hello
thì kết quả lại ra false
var_dump(doesContain('Hello world!', 'H'));
bool(false)
var_dump(doesContain('Hello world!', 'Hello'));
bool(false)
Nguyên nhân là ký tự/chuỗi ký tự (substring) nằm ở vị trí đầu tiên sẽ có giá trị offset bằng 0. Do đó, khi so sánh !=
với giá trị false
, kết quả sẽ ra false
.
Vì vậy, nên dùng strict comparisons (===
, !===
) khi sử dụng hàm strpos()
function doesContain($str, $char) {
return strpos($str, $char) !== false;
}
9. Khai báo tham số không bắt buộc đứng trước tham số bắt buộc
function foo($a = null, $b) {...}
Cách khai báo tham số của hàm như trên là hợp lệ nhưng không hợp lý.
Khi muốn gọi hàm foo()
với giá trị của tham số $a
bằng null
, chúng ta sẽ phải truyền giá trị cho cả hai tham số. Mặc dù tham số đầu tiên đã có giá trị mặc định.
function foo($a = null, $b) {...}
foo(null, $c);
Vì vậy, luôn sắp xếp các tham số không bắt buộc đứng sau cùng khi định nghĩa hàm.
function foo($b, $a = null) {...}
foo($c);
All rights reserved