Cách viết Clean Code trong lập trình C

Cách viết Clean Code trong lập trình C

Cách viết Clean Code trong lập trình C là một số nguyên tắc khi chúng ta học lập trình trên MCU hoặc các nền tảng dùng C khác
Chắc hẳn khi học code online chúng ta thường bị hoang mang bởi việc mỗi người sẽ hướng dẫn một kiểu khác nhau, một cách viết hàm khác nhau. Điều này làm ta khó tiếp cận được kiến thức và hình thành một thói quen viết code xấu, làm code cực kì khó đọc và tìm lỗi.
Vậy nên khi code bất kì một ngôn ngữ gì, ngoài việc follow theo các syntax của ngôn ngữ đó. Cách bạn triển khai code cũng rất quan trọng, nó ảnh hưởng trực tiếp tới khả năng bảo trì, nâng cấp phần mềm.
Trong bài viết này mình sẽ giới thiệu một số Rules và Styles trong lập trình C mình học tập được từ nhiều nguồn và tổng hợp lại. Có thể nó sẽ giúp ích được cho bạn. Ok bắt đầu nhé

Clean Code trong Style (phong cách viết code)

Follow theo 1 Style nhất định sẽ khiến code bạn trông sạch sẽ hơn, dễ đọc hiểu và phân tích.

1. Dấu móc nhọn và lệnh thực thi trong móc nhọn

Sử dụng nhất quán Style C hoặc JAVA.
Với C dấu móc nhọn đặt ngay dưới lệnh và thẳng hàng với lệnh
Với JAVA dấu móc nhọn đầu đặt sau lệnh, ngăn cách bởi một khoảng trắng (dấu cách), móc thứ 2 thẳng hàng với lệnh
Các lệnh thực thi phải thụt lùi 1 tab hoặc 4 space. Nên sử dụng 4 Space nếu sử dụng nhiều trình soạn thảo khác nhau. (vì có thể trình soạn thao sẽ cho 1tab = 2space)
VD: C Style

if(a == b)
{
    a = a++;
    b = b--;
}

Java Style

if(a == b) {
    a = a++;
    b = b--;
}

Với lệnh không có thực thi, vẫn phải viết đúng style và comment lý do không thực thi

if(a == b)
{
//Chờ cho cái gì đó thay đổi
}

2. Dòng trống:

Dòng trống được sử dụng khi phân tách các nội dung khác nhau
– Phân tách khối khai báo và khối lệnh
– Phân tách các lệnh số học và các lệnh điều khiển
– Phân tách các phần của 1 tiến trình, trước mỗi comment đơn mô tả tiến trình đó.(Áp dụng với các hàm phức tạp và dài )VD: hàm khởi tạo LCD bao gồm các bước …..
– Phân tách các lệnh thực thi và lệnh trả về Return.
– Phân tách các lớp khi viết thư viện cho các thiết bị, trước mỗi comment đơn

3. Khoảng trống

Khoảng trống được sử dụng để:
– Phân tách các thành phần khác nhau của dòng lệnh.
– Phân tách các đối tượng đồng dạng ngăn cách nhau bởi dấu phẩy. Dấu cách phải luôn đặt liền sau đấu phẩy
– Phân tách các toán tử và toán hạng. dấu cách đặt đằng trước và sau toán tử
– Không dùng đấu cách với các toán tử ++, — VD: A++; chứ ko dùng A ++;
– Ko dùng dấu cách trước ngoặc và sau ngoặc

for(uint8_t i=0; i < 100; i++)
{
    if(i < 50)
    {
        printf("Cac so nho hon 50 la: %d, i);
    }
}

4. Commnet
Sử dụng // thay vì /*
Chỉ sử dụng /* tại credit đầu mỗi file

/******************************************************************************************************************
@File:  	CLCD 8BIT (Character LCD 8Bit Mode)
@Author:  Khue Nguyen
@Website: khuenguyencreator.com
@Youtube: https://www.youtube.com/channel/UCt8cFnPOaHrQXWmVkk-lfvg

Huong dan su dung:
- Su dung thu vien HAL
- Khoi tao bien LCD: CLCD_Name LCD1;
- Khoi tao LCD do:
Che do 8 bit
CLCD_8BIT_Init(&LCD1, 16, 2, CS_GPIO_Port, CS_Pin, EN_GPIO_Port, EN_Pin,
                                    D0_GPIO_Port, D0_Pin, D1_GPIO_Port, D1_Pin,
                                    D2_GPIO_Port, D2_Pin, D3_GPIO_Port, D3_Pin,
                                    D4_GPIO_Port, D4_Pin, D5_GPIO_Port, D5_Pin,
                                    D6_GPIO_Port, D6_Pin, D7_GPIO_Port, D7_Pin);
Che do 4 bit									
CLCD_4BIT_Init(&LCD1, 16, 2, CS_GPIO_Port, CS_Pin, EN_GPIO_Port, EN_Pin,
                                    D4_GPIO_Port, D4_Pin, D5_GPIO_Port, D5_Pin,
                                    D6_GPIO_Port, D6_Pin, D7_GPIO_Port, D7_Pin);
- Su dung cac ham truyen dia chi cua LCD do: 
CLCD__SetCursor(&LCD1, 0, 0);
CLCD_WriteString(&LCD1,"Hello anh em");	
******************************************************************************************************************/

Các trường hợp dùng comment
– Hàm phức tạp có nhiều bước thực thi
– Hàm có những lưu ý riêng
– Trong điều kiện không xử lý gì (lý do ko xử lý)
– Phân tách các khối định nghĩa
– Phân tách các lớp
5. Độ dài của một dòng
Hàm hoặc câu lệnh quá dài nên viết thành nhiều hàng, đảm bảo lệnh được nhìn thấy rõ ràng trong trình soạn thảo mà ko cần kéo chuột
Các hàng sau nên thụt vào 2 Tab hoặc căn chỉnh so với các giá trị cùng loại

CLCD_8BIT_Init(&LCD1, 16, 2, CS_GPIO_Port, CS_Pin, EN_GPIO_Port, EN_Pin,
                                    D0_GPIO_Port, D0_Pin, D1_GPIO_Port, D1_Pin,
                                    D2_GPIO_Port, D2_Pin, D3_GPIO_Port, D3_Pin,
                                    D4_GPIO_Port, D4_Pin, D5_GPIO_Port, D5_Pin,
                                    D6_GPIO_Port, D6_Pin, D7_GPIO_Port, D7_Pin);

Clean code bằng cách đặt những Rules (luật) cho việc lập trình

1. Nguyên tắc đặt tên hàm

– Tên hàm ko được trùng tên với các thư viện chuẩn của C
– Tên hàm không nên chứa các từ khóa của C
– Tên hàm bắt buộc dùng tiếng anh

– Các Module phải viết hoa hoàn toàn và phân tách nhau bởi dấu gạch dưới
– Phần chức năng hàm viết Hoa chữ đầu để phân tách các từ theo phong cách Pascal
Format: 
MODULE_FuncPurpose(type para1, type para2 ….)

DS18B20_ReadTemp()

Với hàm thuộc module con hoặc của một loại LIB cụ thể cần thêm tên module cha vào trước

HAL_DS18B20_ReadTemp(); //Thư viện DS18B20 viết riêng với thư viện HAL

2. Nguyên tắc trình bày hàm

– Comment trước mỗi hàm mô tả chức năng và các tham số truyền vào. Các lưu ý khi sử dụng hàm.(Sử dụng với các hàm phức tạp)
– Tất cả các hàm phải có giá trị trả về.
– Với hàm thực thi. Trả về OK khi hoàn thành, ERR khi không hoàn thành
– Với hàm so sánh, trả về. True – false
– Chỉ được đặt 1 lệnh return ở cuối mỗi hàm, giá trị trả về phải khai báo là biến cục bộ trong hàm

– Trước return phải để cách 1 dòng trắng
– Không sử dụng Return với hàm đằng sau. 

– Nên ép kiểu cho Return nếu dữ liệu trả về không đồng nhất

int PinStatus;
PinStatus = Hal_GPIO_Readpin(BUTTON_PORT, BUTTON_PIN);


Return PinStatus;

Tránh việc sử dụng 

Return Hal_GPIO_Readpin(BUTTON_PORT, BUTTON_PIN);

3. Nguyên tắc tạo một biến

– Nên sử dụng dạng dữ liệu ko âm. unsigned
– Sử dụng kiểu dữ liệu định nghĩa lại của c: uint8_t
– Luôn khởi tạo giá trị đầu tiên cho biến, trừ trường hợp đặc biệt cần các biến không bị mất giá trị khi reset
– Các biến chung kiểu cũng nên khai báo riêng không được khai báo cùng nhau:
VD:

uint8_t SoA, SoB = 1;// sai
uint8_t SoA = 1;
uint8_t SoB = 1; // dung

– Các biến đại diện cho 1 đối tượng nào đó nên khai báo kiểu typedef struct.
– Nên sử dụng typedef với các kiểu biến struct, union, enum
– Không được sử dụng từ tối nghĩa khi đặt tên. VD: x,y, a,b,c,tmp …
– Hạn chế sử dụng extern để trao đổi biến toàn cục vì sẽ làm giảm tính độc lập của chương trình.

4. Nguyên tắc Đặt tên biến

Biến toàn cục: [Type]_MODULE_VarPur = Index;
Trong đó Type là kiểu dữ liệu viết tắt:

uint8_t u8
uint16_t u16
string str
char c
unsigned char uc
foalt f

MODULE. Module chứa biến toàn cục đó, viết hoa toàn bộ
VarPur: chức năng của biến đó

uint8_t u8_DS18B20_Temp = 100;

Biến Cục bộ: [Type]_VarPur = Index;

uint8_t u8_Temp = 100;

Biến Kiểu Mảng:  thêm tiền tố Ar+Số chiều của mảng trước tên mỗi biến

Lưu ý: khởi tạo độ dài của mảng nên là một hằng số, hoặc một số được định nghĩa lại

Ar[NumOfDirect]_[Type]_MODULE_VarPur = {};

#define BUFFER_LENGHT 100
Ar1_u8_GLCD_Buffer[BUFFER_LENGHT] = {0,1,2,3,4}

Kiểu Con trỏ:  thêm tiền tố p trước tên mỗi biến

p_[Type]_MODULE_VarPur = Index;

p_u8_GLCD_Address = 0x01;

5. Nguyên tắc đặt tên hằng

– Hằng số phải được viết hoa toàn bộ.
– Với phương pháp định nghĩa: 
– Sử dụng để định nghĩa địa chỉ các thanh ghi device, lệnh… được fix bởi phần cứng

Cú pháp: #define MODULE_CONSTPUR Index

#define CLCD_RETURNHOME 0x02

– Với phương pháp khai báo, sử dụng tiêu chuẩn của biến nhưng viết hoa.

– Sử dụng cho các giá trị có thể thay đổi bởi người lập trình

Cú pháp: CST_[Type]_MODULE_CSTPUR = INDEX;

const uint8_t CST_U8_GLCD_CLEARCMD = 0x01;

6. Số trong lập trình C

Các con số đặc biệt nên được định nghĩa thành dạng chữ, để tiện cho việc quản lý

Circle = R*3.1415 // Khong nen
//Nen dung
#define PI 3.1415
Circle = R*PI;

Với các số tên giống nhau nhưng giá trị khác nhau có thể bị trùng lặp với module khác nên định nghĩa theo kiểu hằng. Nghĩa là có thêm module chứa nó phía trước

#define CST_U8_CIRCLE_PI 3.14

Nguồn tham khảo cách viết clean code khác 

Các bạn có thể tìm kiếm thêm các nguồn với Keyword: Coding Convention in C và Clean Code in C 

Một số bài viết hay như:

https://enos.itcollege.ee/~jpoial/oop/naited/Clean%20Code.pdf

https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html

Six Rules in C

Kết 

Viết code là công việc không chỉ cho máy tính hoạt động, mà còn cho các lập trình viên khác xem và hiểu. Vậy nên nếu các bạn follow theo các Style và Rules như trên, lập trình viên khác sẽ rất dễ hiểu và code theo dự án của bạn

Đừng quên gia nhập Hội Anh Em Nghiện Lập trình để giao lưu và học hỏi nhé.

 

5/5 - (4 bình chọn)
0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
guest

2 Góp ý
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất
Phản hồi nội tuyến
Xem tất cả bình luận
Cynthia Kleppen
3 năm trước

exceptional article