Hướng dẫn dùng touh trong PHP

Danh sách các bài viết trong chuyên mục Tự Học PHP, đây là những bài viết mới nhất được cập nhật trong mục PHP.

PHP là một ngôn ngữ lập trình phía Server dùng để xây dựng các ứng dụng Website. Điểm mạnh của PHP là tính cộng đồng của nó cao, nghĩa là trên thế giới có khá nhiều lập trình viên sử dụng PHP để xây dựng dự án cho riêng họ hoặc cho khách hàng của họ. Ngoài ra một điểm mạnh nữa của PHP đó là có khá nhiều CMS, Framework được xây dựng từ PHP giúp rút gọn quá trình tạo một website, vì vậy có khá nhiều lập trình viên lựa chọn ngôn ngữ này.

Trước khi bắt tay vào học lập trình PHP thì bạn phải chuẩn bị tinh thần để thức khuya vì đa số lập trình viên đều như vậy, họ làm việc và học hỏi không kể ngày đêm. Điều này khá hai cho sức khỏe nên bạn cần cân nhắc và lên lịch học cho phù hợp với bản thân nhé, chơi thể thao nhiều vào để giảm stress.

Hiện nay trên internet có rất nhiều video và bài viết học PHP miễn phí hoặc có phí nên bạn chỉ cần bỏ chút thời gian và chút tiền là có thể học được ngôn ngữ này. Tại sao tôi lại nói nó dễ học hơn ngôn ngữ lập trình khác? Rất đơn giản vì PHP có tính học và ứng dụng ngay, nghĩa là bạn có thể vừa học vừa ứng dụng nó vào thực tế luôn, điều này với ngôn ngữ như C không thể được vì để sử dụng C xây dựng project thì bạn phải có một nền tảng vững chắc.

Đây là những nguyên lý kỹ thuật phần mềm, được trích từ cuốn sách Clean Code của tác giả Robert C. Martin [thường gọi là Uncle Bob] rất thích hợp cho ngôn ngữ PHP. Tài liệu này không phải là sách hướng dẫn về phong cách viết code, mà là hướng dẫn cách làm thế nào để viết phần mềm dễ đọc, dễ sử dụng lại, và dễ cải tiến trong PHP.

Bạn không cần phải tuân theo tất cả các nguyên tắc trong tài liệu này. Đây chỉ đơn giản là những hướng dẫn, nhưng dù sao nó cũng là đúc kết từ nhiều năm kinh nghiệm của tác giả.

Repository này lấy cảm hứng từ clean-code-javascript

Lưu ý: Dù nhiều lập trình viên còn sử dụng PHP 5, nhưng nhiều ví dụ trong đây chỉ chạy được trên PHP 7.1+.

Tuyển dụng php cần gấp làm việc online

Biến

Sử dụng tên biến có ý nghĩa và dễ hiểu

Chưa tốt:

$ymdstr = $moment->format['y-m-d'];

Tốt:

$currentDate = $moment->format['y-m-d'];

Sử dụng cùng từ vựng cho cùng ột loại biến

Chưa tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];

Tốt:

getUser[];

Đặt tên sao cho dễ tìm kiếm [phần 1]

Thường thì chúng ta sẽ đọc code nhiều hơn viết code. Nên điều quan trọng là code chúng ta viết ra phải dễ đọc và dễ tìm kiếm. Nếu không đặt tên biến có ý nghĩa và làm chương trình dễ hiểu, chúng ta sẽ gây khó cho những lập trình viên khác. Do đó mỗi khi đặt tên biến, hàm thì hãy đặt có ý nghĩa.

Chưa tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];

Tốt:

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];

Đặt tên sao cho dễ tìm kiếm [phần 2]

Chưa tốt:

// Lại nữa, 4 nghĩa là cái giề đây?
if [$user->access & 4] {
    // ...
}

Tốt:

class User
{
    const ACCESS_READ = 1;
    const ACCESS_CREATE = 2;
    const ACCESS_UPDATE = 4;
    const ACCESS_DELETE = 8;
}

if [$user->access & User::ACCESS_UPDATE] {
    // do edit ...
}

Đặt tên biến dễ hiểu

Tức là đặt tên biến sao cho đọc vô là hiểu nó là gì và nó dùng để làm gì. Không cần phải suy nghĩ, suy diễn.

Chưa tốt:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*[.+?]\s*[\d{5}]$/';
preg_match[$cityZipCodeRegex, $address, $matches];

saveCityZipCode[$matches[1], $matches[2]];

Không tệ lắm:

Tốt hơn một chút, nhưng vẫn còn phụ thuộc nhiều vào regex.

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*[.+?]\s*[\d{5}]$/';
preg_match[$cityZipCodeRegex, $address, $matches];

[, $city, $zipCode] = $matches;
saveCityZipCode[$city, $zipCode];

Tốt:

Đã giảm phụ thuộc vào regex bằng “naming subpatterns”.

$currentDate = $moment->format['y-m-d'];
0

Tránh lồng [nesting] quá nhiều và nên return sớm [phần 1]

Quá nhiều if else lồng nhau sẽ khiến code tăng độ phức tạp, khó debug. Giảm sự phức tạp bằng cách giảm số if else lồng nhau xuống ít nhất có thể. Return sớm chính là một cách giảm số lần lồng nhau.

Chưa tốt:

$currentDate = $moment->format['y-m-d'];
1

Tốt:

$currentDate = $moment->format['y-m-d'];
2

Tránh lồng [nesting] quá nhiều và nên return sớm [phần 2]

Chưa tốt:

$currentDate = $moment->format['y-m-d'];
3

Tốt:

$currentDate = $moment->format['y-m-d'];
4

Tránh hack não người đọc

Đừng khiến người đọc code phải khó khăn để hiểu ý nghĩa của biến. Tên biến càng rõ ràng càng tốt.

Chưa tốt:

$currentDate = $moment->format['y-m-d'];
5

Tốt:

$currentDate = $moment->format['y-m-d'];
6

Đừng thêm những nội dung không cần thiết

Nếu tên của class/object đã rõ ràng, không nên lặp lại chúng trong tên biến.

Chưa tốt:

$currentDate = $moment->format['y-m-d'];
7

Tốt:

$currentDate = $moment->format['y-m-d'];
8

Sử dụng đối số mặc định thay vì phải kiểm tra bằng biểu thức điều kiện

Chưa tốt:

Chưa tốt vì 

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
9 có thể bị 
$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
0.

$currentDate = $moment->format['y-m-d'];
9

Không tệ lắm:

Cái này tốt hơn cái trước, nhưng nó nên quản lý được giá trị của biến thì tốt hơn.

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
0

Tốt:

Bạn có thể sử dụng type hinting và chắc chắn 

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
9 sẽ không bị 
$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
0.

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
1

So sánh

Sử dụng identical comparison

Chưa tốt:

Sử dụng simple comparison [so sánh đơn giản]

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
2

Phép so sánh $a != $b trả về 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
3 nhưng trong thực tế thì nó phải là 
$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
4. Chuỗi ’42’ thì phải khác số 42 chứ đúng không.

Tốt:

Sử dụng identical comparison [so sánh giống hệt nhau] để so sánh cả kiểu dữ liệu và giá trị

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
3

Hàm

Đối số của hàm [ít hơn hoặc bằng 2 là lý tưởng]

Giới hạn số lượng đối số [parameters] của hàm vô cùng quan trọng bởi vì nó giúp dễ test hơn. Có nhiều hơn 3 đối số dẫn đến một tổ hợp rất nhiều trường hợp khác nhau cần phải test.

Lý tưởng nhất là khi hàm không có đối số nào. Một hoặc hai đối số là ok, còn ba thì nên hạn chế. Bất cứ khi nào hàm có nhiều hơn 3 đối số thì cần phải xem xét tìm cách giảm bớt lại. Bởi vì nếu hàm có nhiều hơn hai đối số thì nó phải xử lý rất nhiều.

Chưa tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
4

Tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
5

Hàm chỉ thực hiện một chức năng

Đây là nguyên tắc quan trọng nhất trong phát triển phần mềm. Khi hàm thực hiện nhiều hơn một chức năng, chúng khó biên dịch, kiểm tra và biết được nguyên nhân lỗi. Khi bạn tạo hàm chỉ với một chức năng, sẽ dễ dàng refactor hơn và code sẽ dễ đọc hơn. Nếu làm được điều này thì bạn sẽ tốt hơn nhiều lập trình viên khác.

Chưa tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
6

Tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
7

Tên hàm nên thể hiện chức năng của hàm

Chưa tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
8

Tốt:

getUserInfo[];
getUserData[];
getUserRecord[];
getUserProfile[];
9

Hàm chỉ nên có độ trừu tượng một cấp

Khi bạn có độ trừu tượng nhiều hơn một cấp thì hàm thường phải làm quá nhiều việc. Hãy chia tách hàm ra thành nhiều phần để dễ sử dụng lại và dễ test.

Chưa tốt:

getUser[];
0

Cũng chưa tốt:

Chúng ta đã thực hiện tách ra vài hàm, nhưng hàm 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
5 vẫn còn khá phức tạp và khó test.

getUser[];
1

Tốt:

Giải pháp tốt nhất là chuyển các phần thành các dependencies của hàm 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
5

getUser[];
2

Đừng sử dụng cờ như là một đối số của hàm

Cờ dùng để nói rằng hàm này thực hiện nhiều hơn một công việc. Nhưng hàm thì chỉ nên xử lý một công việc mà thôi. Do đó hãy chia tách hàm của bạn nếu như chúng có nhiều luồng code phân biệt bằng boolean[true/false].

Chưa tốt:

getUser[];
3

Tốt:

getUser[];
4

Tránh tác dụng phụ

Một hàm sinh ra tác dụng phụ nếu nó thực hiện thêm việc khác ngoài việc lấy giá trị vào và trả về một hoặc nhiều giá trị khác. Tác dụng phụ có thể là viết vào một file nào đó, sửa đổi biến global, hoặc vô tình chuyển hết tiền của bạn cho người lạ nào đó.

Vậy nếu bây giờ bạn cần hàm thực hiện các tác dụng phụ đó thì sao. Giống như ví dụ trước, bạn cần ghi vào file. Điều bạn cần làm là tập trung những việc này lại một chỗ. Đừng viết vài hàm và vài lớp chỉ để ghi vào vài file cụ thể. Hãy viết một service để làm điều đó. Một và chỉ một service.

Hãy tránh những sai lầm phổ biến như: chia sẻ trạng thái giữa các object mà không tuân theo cấu trúc nào, sử dụng kiểu dữ liệu có thể thay đổi/bị thay đổi dễ dàng, không tổng hợp các tác dụng phụ có thể xảy ra khi viết hàm.

Chưa tốt:

getUser[];
5

Tốt:

getUser[];
6

Đừng viết hàm global

Dùng nhiều hàm global là bad practice với nhiều ngôn ngữ bởi vì có thể gây xung đột với thư viện khác và người sử dụng API của bạn không hề hay biết gì cho đến khi nhận được thông báo lỗi.

Hãy xem xét ví dụ sau: bạn sẽ làm gì nếu muốn trả về một mảng.

Bạn có thể viết hàm global như 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
7, nhưng nó có thể xung đột với thư viện khác thực hiện cùng chức năng.

Chưa tốt:

getUser[];
7

Tốt:

getUser[];
8

Tạo instance của lớp 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
8

getUser[];
9

Và bây giờ sử dụng instance 

$json = $serializer->serialize[$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE];
8 trong ứng dụng của bạn.

Đừng sử dụng Singleton pattern

Singleton là một anti-pattern. Trích đoạn từ Brian Button:

    1. Chúng thường được sử dụng như global instance, vì sao lại Chưa tốt? Bởi vì bạn ẩn dependencies của ứng dụng bên trong code của bạn, thay vì thông qua interfaces
    2. Chúng vi phạm single responsibility principle: bởi vì thực tế là chúng điều khiển những gì chúng tạo ra và vòng đời của nó
  1. Chúng đã tạo ra kiểu code coupling. Đây là một sự giả mạo và được giấu bằng cách tạo ra nhiều trường hợp test khó khăn hơn.
  2. Chúng giữ trạng thái suốt vòng đời của ứng dụng. Bạn nên kết thúc sớm testing khi lỗi. Nhưng Singleton thì lại duy trì trạng thái nên nó Chưa tốt.

Đây là một ý kiến khác của Misko Hevery về gốc rễ của vấn đề.

Chưa tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
0

Tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
1

Tạo instance của lớp 

// Lại nữa, 4 nghĩa là cái giề đây?
if [$user->access & 4] {
    // ...
}
0 và cấu hình chúng với DSN.

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
2

Và bây giờ sử dụng instance 

// Lại nữa, 4 nghĩa là cái giề đây?
if [$user->access & 4] {
    // ...
}
0 cho ứng dụng của bạn.

Đóng gói điều kiện

Chưa tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
3

Tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
4

Tránh điều kiện phủ định

Chưa tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
5

Tốt:

// Oh man, 448 là cái giề vậy?
$result = $serializer->serialize[$data, 448];
6

Tránh dùng điều kiện

Điều này có vẻ không khả quan. Hầu hết mọi người sẽ thắc mắc, “làm sao có thể làm gì đó mà không có 

// Lại nữa, 4 nghĩa là cái giề đây?
if [$user->access & 4] {
    // ...
}
2?” Bạn có thể dùng tính đa hình để hoàn thành việc đó trong khá nhiều trường hợp. Câu hỏi thứ hai là, “ồ ngon nhưng tại sao phải làm thế?” Bởi vì khái niệm clean code mà ta đã học trước đây: một hàm chỉ nên thực hiện một chức năng. Khi bạn có một lớp hoặc hàm chứa 
// Lại nữa, 4 nghĩa là cái giề đây?
if [$user->access & 4] {
    // ...
}
2, tức là bạn đang muốn nó thực hiện nhiều việc. Luôn nhớ, chỉ một mà thôi.

Chủ Đề