- 4.1. Cấu trúc của Project
- 4.2. Quy tắc đặt tên
- 4.3. Hàm
- 4.4. Chú thích
- 4.5. Đối tượng và Cấu trúc dữ liệu
- 4.6. SOLID
- 4.7. Xử lý ngoại lệ
Clean Code hay Code sạch, là code thể hiện rõ ý đồ của lập trình viên, đồng thời khiến người khác có thể hiểu một dễ dàng
- Dễ dàng trong việc bảo trì và phát triển
- Giúp người khác dễ đọc hiểu code hơn
- Thể hiện trình độ của lập trình viên
- Focus: Code được viết để giải quyết một vấn đề cụ thể
- Simple: Càng đơn giản càng tốt
- Testable: Trực quan và dễ dàng cho việc test
- src/main/java: source files
- src/main/resources: resource files, ví dụ như properties, temple
- src/test/java: test source files
- src/test/resources: test resource files, ví dụ như properties, temple
Không tốt
int d; // elapsed time in days
Tốt
int elapsedTimeInDays;
int daysSinceCreation;
Không tốt
String yyyymmdstr = new SimpleDateFormat("YYYY/MM/DD").format(new Date());
Tốt
String currentDate = new SimpleDateFormat("YYYY/MM/DD").format(new Date());
Không tốt
// what is 86400000?
setTimeout(blastOff, 86400000);
Tốt
public static final int MILLISECONDS_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
Không tốt
getUserInfo();
getClientData();
getCustomerRecord();
Tốt
getUser();
Tên lớp và các đối tượng nên sử dụng danh từ hoặc cụm danh từ, như Customer, WikiPage, Account, và AddressParser
Hãy sử dụng các thuật ngữ khoa học, design pattern,... cho việc đặt tên
public class userService{
}
public class userRepository{
}
Tên các phương thức nên có động từ hoặc cụm động từ như postMessage
, deleteMessage
,
hoặc save. Các phương thức truy cập, chỉnh sửa thuộc tính phải được đặt tên cùng với get
, set
và
is
theo tiêu chuẩn của Javabean.
Giới hạn số lượng param của hàm là một điều cực kì quan trọng bởi vì nó làm cho hàm của bạn trở nên dễ test hơn. Trường hợp có nhiều hơn 3 params có thể dẫn đến việc phải test hàng tấn test case khác nhau với những đối số riêng biệt.
Đây là quy định quan trọng nhất của kỹ thuật phần mềm. Khi một hàm thực hiện nhiều hơn 1 việc, chúng sẽ trở nên khó khăn hơn để viết code, test, và suy luận. Khi có thể tách biệt một hàm để chỉ thực hiện một hành động, thì sẽ dễ dàng hơn để tái cấu trúc và code của bạn sẽ dễ đọc hơn nhiều.
Không tốt:
public void emailClients(List<Client> clients) {
for (Client client : clients) {
Client clientRecord = repository.findOne(client.getId());
if (clientRecord.isActive()){
email(client);
}
}
}
Tốt:
public void emailClients(List<Client> clients) {
for (Client client : clients) {
if (isActiveClient(client)) {
email(client);
}
}
}
private boolean isActiveClient(Client client) {
Client clientRecord = repository.findOne(client.getId());
return clientRecord.isActive();
}
Không tốt:
private void addToDate(Date date, int month){
//..
}
Date date = new Date();
// Khó để biết được hàm này thêm gì thông qua tên hàm.
addToDate(date, 1);
Tốt:
private void addMonthToDate(Date date, int month){
//..
}
Date date = new Date();
addMonthToDate(1, date);
Tuyệt đối tránh những dòng code trùng lặp. Code trùng lặp thì không tốt bởi vì nếu cần thay đổi cùng một logic, chúng ta phải sửa ở nhiều hơn một nơi.
Không tốt
// kiểm tra nhân viên có đủ điều kiện để nhận các quyền lợi không
if ((employee.flags & HOURLY_FLAG) &&
(employee.age > 65))
Tốt
if (employee.isEligibleForFullBenefits())
Không tốt:
// Tạo list customerNames
List<String> customerNames = Arrays.asList('Bob', 'Linda', 'Steve', 'Mary');
// Sử dụng stream dể tìm người đầu tiên
Optional<String> firstCustomer = customerNames.stream().findFirst();
// Nếu stream rỗng thì in ra no value
if (firstCustomer.isPresent()) {
System.out.println(firstCustomer.get());
} else {
System.out.println("no value");
}
Không tốt:
List<String> customerNames = Arrays.asList('Bob', 'Linda', 'Steve', 'Mary');
Optional<String> firstCustomer = customerNames.stream().findFirst();
if (firstCustomer.isPresent()) {
System.out.println(firstCustomer.get());
} else {
System.out.println("no value");
}
Không tốt:
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Tốt:
doStuff();
Thay vì sử dụng những chú thích nhật kí, chúng ta có thể sử dụng công cụ như Git. Và git log
có thể xem lại lịch sử của Soure code.
Không tốt:
/**
* 2016-12-20: Removed monads, didn't understand them (RM)
* 2016-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
function combine(a, b) {
return a + b;
}
Tốt:
function combine(a, b) {
return a + b;
}
Không tốt
/////////////////////////////////////////////
//Khai báo
/////////////////////////////////////////////
int dayOfMonth;
/////////////////////////////////////////////
//Các phương thức
/////////////////////////////////////////////
public int getDayOfMonth(){
return dayOfMonth;
}
Tốt
int dayOfMonth;
public int getDayOfMonth(){
return dayOfMonth;
}
- Khi bạn muốn thực hiện nhiều hơn việc lấy một thuộc tính của đối tượng, bạn không cần phải tìm kiếm và thay đổi mỗi accessor trong codebase của bạn.
- Làm cho việc thêm các validation đơn giản khi thực hiện trên một tập hợp.
- Đóng gói các biểu diễn nội bộ.
- Dễ dàng thêm log và xử lí lỗi khi getting và setting.
- Kế thừa lớp này, bạn có thể override những hàm mặc định.
- Bạn có thể lazy load các thuộc tính của một đối tượng, lấy nó từ server.
Tránh sử dụng hàm constructor với nhiều hơn 3 đối số truyền vào thay vì vậy hãy áp dụng Builder Pattern
SOLID là viết tắt của 5 chữ cái đầu trong 5 nguyên tắc thiết kế hướng đối tượng. Giúp cho lập trình viên viết ra những đoạn code dễ đọc, dễ hiểu, dễ maintain. Nó được đưa ra bởi Robert C. Martin và Michael Feathers. 5 nguyên tắc đó bao gồm:
- Single responsibility priciple (SRP)
- Open/Closed principle (OCP)
- Liskov substitution principe (LSP)
- Interface segregation principle (ISP)
- Dependency inversion principle (DIP)
Mỗi lớp chỉ nên chịu trách nhiệm về một nhiệm vụ cụ thể nào đó mà thôi.
Ví dụ
public class DBHelper {
public Connection openConnection() {};
public List<User> getUserByUsername(String user) {};
public List<Role> getRole() {};
public void closeConnection() {};
}
public class DBConnection{
public Connection openConnection() {};
public void closeConnection() {};
}
public class UserRepository{
public List<User> getUserByUsername(String user) {};
}
public class RoleRepository{
public List<Role> getRole() {};
}
Không được sửa đổi một Class có sẵn, nhưng có thể mở rộng bằng kế thừa.
Ví dụ
Trước đây, logic xử lý tính phí vận chuyển của một đơn hàng được đặt luôn bên trong class Order.
public class Order {
public long calculateShipping(ShippingMethod shippingMethod) {
if (shippingMethod == GROUND) {
// Calculate for ground shipping
} else if (shippingMethod == AIR) {
// Calculate for air shipping
} else {
// Default
}
}
}
Giả sử hệ thống cần bổ sung thêm một phương thức vận chuyển mới, chúng ta lại phải bổ sung một case nữa trong method calculateShipping
. Điều này sẽ làm code trở nên rất khó quản lý.
Thay vào đó, chúng ta nên tách rời logic xử lý tính phí vận chuyển vào một interface Shipping
chẳng hạn. Interface Shipping
sẽ có nhiều implementation ứng với từng hình thức vận chuyển: GroundShipping
, AirShipping
, ...
public interface Shipping {
long calculate();
}
public class GroundShipping implements Shipping {
@Override
public long calculate() {
// Calculate for ground shipping
}
}
public class AirShipping implements Shipping {
@Override
public long calculate() {
// Calculate for air shipping
}
}
public class SeaShipping implements Shipping {
@Override
public long calculate() {
// Calculate for Sea shipping
}
}
public class Order {
private Shipping shipping;
public long calculateShipping(ShippingMethod shippingMethod) {
// Find relevant Shipping implementation then call calculate() method
}
}
Như ví dụ trên, khi bổ sung dịch vụ vận chuyển bằng đường biển, ta chỉ cần tạo thêm 1 class SeaShipping
và implements interface Shipping
.
Các đối tượng (instance) kiểu class con có thể thay thế các đối tượng kiểu class cha mà không gây ra lỗi.
Ví dụ:
public interface Animal{
void fly();
}
public class Bird implements Animal(){
@Override
void fly();
}
public class Dog implements Animal(){
@Override
void fly(); //?? Dog cannot fly
}
Như ta biết, chó không thể bay được. Vậy như ví dụ trên đã vi phạm Nguyên lí thay thế Liskov.
Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.
Ví dụ:
interface Animal {
void eat();
void run();
void fly();
}
Chúng ta có 2 class Dog
và Snake
implement interface Animal
. Nhưng thật vô lý, Dog
thì làm sao có thể fly()
, cũng như Snake không thể nào run()
được? Thay vào đó, chúng ta nên tách thành 3 interface như thế này:
interface Animal {
void eat();
}
interface RunnableAnimal extends Animal {
void run();
}
interface FlyableAnimal extends Animal {
void fly();
}
Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào abstraction.
Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại (Các class giao tiếp với nhau thông qua interface (abstraction), không phải thông qua implementation.)
Ví dụ
// Interface
public interface IDatabase {
void Save(int orderId);
}
public interface ILogger {
void LogInfo(string info);
}
public interface IEmailSender {
void SendEmail(int userId);
}
// Các Module implement các Interface
public class Logger implements ILogger{
public void LogInfo(string info) {
}
}
public class Database implements IDatabase{
public void Save(int orderId) {
}
}
public class EmailSender implements IEmailSender{
public void SendEmail(int userId) {}
}
public void Checkout(int orderId, int userId){
IDatabase db = new Database();
db.Save(orderId);
ILogger log = new Logger();
log.LogInfo("Order has been checkout");
IEmailSender es = new EmailSender();
es.SendEmail(userId);
}
Đừng bỏ qua những ngoại lệ mà có thể bắt được
Không tốt
try{
inputAge();
} catch (Exception e){
}
Tốt
try{
inputAge();
} catch (InputMissMatchException e){
} catch (AgeMustBePositive e){
} catch (Exception e){
}
Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)
https://github.com/leonardolemie/clean-code-java