Tạm biệt dirty code trong lập trình JS
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Khi chúng ta bước chân vào thế giới lập trình, chúng ta có thể thấy được những điều hữu ích mà nó đem lại cho hàng triệu người. Chỉ bằng việc thao tác với các đoạn code, lập trình đã giúp cho cuộc sống của chúng ta trở nên dễ dàng hơn.
Tuy nhiên, "năng lực lớn đi đôi với trách nhiệm lớn". Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Có một số thói quen nhỏ trong lập trình có thể gây tác động tiêu cực liên tục đến code mà chúng ta viết và sản phẩm mà chúng ta tạo ra. Mình đã trải nghiệm những vấn đề này trực tiếp.
Hôm nay mình sẽ chia sẻ những vấn đề này và lý do tại sao bạn nên tránh chúng bằng mọi giá.
1. Sử dụng var
thay vì let
và const
Bạn nên chỉ sử dụng let và const bởi một vài lý do sau:
- Scope rõ ràng hơn.
- Nó không tạo ra các đối tượng global.
- Với
const
- nó hiển thị lỗi ngay khi chúng ta cố gắng khai báo lại một biến.
// Sử dụng var:
var x = 10;
if (true) {
var x = 20;
}
console.log(x); // Output: 20
// Khi chúng ta sử dụng var, biến có thể được khai báo lại và ghi đè giá trị của nó trong cùng phạm vi.
// Sử dụng let:
let y = 10;
if (true) {
let y = 20;
}
console.log(y); // Output: 10
// Khi chúng ta sử dụng let, biến chỉ có thể được khai báo lại trong cùng khối lệnh và không ghi đè giá trị của nó ở ngoài khối lệnh đó.
// Sử dụng const:
const z = 10;
z = 20; // Error: Assignment to constant variable.
// Khi chúng ta sử dụng const, biến không thể được khai báo lại và không thể được ghi đè giá trị của nó.
Kể cả khi bạn muốn code của mình hoạt động ổn định với các trình duyệt cũ như IE11 thì bạn cũng không nên vứt bỏ nó. Hãy sử dụng let
/const
kèm với polyfill. Tuy vậy, 2023 rồi ai mà xài IE cũ nữa đâu, cả Microsoft cũng đã có kế hoạch xoá sạch IE rồi :D
2. Dùng comments để mô tả code
Comments (hay chú thích) là một phần cơ bản trong quá trình xây dựng phần mềm, nó giúp chúng ta hiểu rõ hơn về đoạn mã mà chúng ta đang đọc.
Tuy nhiên, chúng ta không nên mắc phải sai lầm khi giải thích từng bước mà code của chúng ta đang làm, mà chúng ta phải tạo ra code dễ đọc. Comments chỉ nên cung cấp context (bối cảnh).
- Tránh sự lặp lại trong comments của bạn. Đừng viết những gì bạn đang làm, hãy viết lý do tại sao bạn làm nó.
- Hãy đặt các tên biến/function/class mô tả một cách rõ ràng công việc của chúng, thay vì ngồi viết một đống comments.
- Hãy cố gắng viết code rõ ràng và clean hết sức có thể. Hãy nhớ, không phải code càng ngắn càng tốt, mà code rõ ràng, sạch sẽ, dễ thay đổi, dễ quản lý mới là tốt.
- Viết comments rõ ràng, tốt nhất là cùng một ngôn ngữ (tiếng Anh, tiếng Việt...). Đừng viết chỗ này tiếng Anh, chỗ kia tiếng Việt, chỗ khác lại phang tiếng Trung Quốc vô :D
- Comments nên súc tích, gọn gàng. Theo thời gian, comments thường không được bảo trì, code lại thay đổi thường xuyên.
3. Sử dụng so sánh bằng (==
) thay vì so sánh bằng nghiêm ngặt (===
)
Mặc dù chúng có vẻ rất giống nhau về hình thức, tuy nhiên chúng lại có những điều khác nhau về cách hoạt động.
Toán tử so sánh bằng (==) sẽ cố gắng chuyển đổi các phần tử so sánh về cùng kiểu, sau đó thực hiện so sánh xem có giống nhau hay không. Điều này có thể gây ra một vài lỗi vớ vẩn không đáng có.
So sánh bằng nghiêm ngặt (===) luôn kiểm tra xem các toán hạng có các kiểu dữ liệu và giá trị khác nhau hay không.
Nên tránh sử dụng toán tử so sánh bằng (==) vì nó có thể gây ra các kết quả không mong muốn khi các phần tử so sánh có kiểu dữ liệu khác nhau. Nếu bạn sử dụng toán tử so sánh bằng nghiêm ngặt (===), bạn sẽ có thể kiểm tra xem các phần tử so sánh có giống nhau hoàn toàn không, bao gồm cả kiểu dữ liệu của chúng.
let x = 5;
let y = "5";
console.log(x == y); // true
console.log(x === y); // false
console.log([] == 0); // true
console.log([] === 0); // false
4. Không sử dụng optional chaining
Toán tử optional chaining (?) cho phép chúng ta đọc giá trị của một thuộc tính nằm sâu trong chuỗi các đối tượng liên kết mà không cần phải kiểm tra từng tham chiếu trong chuỗi đó.
const person = {
name: "John",
address: {
city: "New York",
street: "5th Avenue",
zip: 12345
}
};
const zipCode = person?.address?.zip; // 12345
const anotherInvalidAccess = person.invalidAddress.zip // Error
const anotherInvalidAccess = person.invalidAddress?.zip // undefined
Chúng ta nên sử dụng optional chaining trong các tình huống khi chúng ta không chắc chắn rằng một thuộc tính nào đó tồn tại trong đối tượng hoặc nó có giá trị là gì.
Sử dụng optional chaining giúp chúng ta tránh được những lỗi không mong muốn khi truy cập vào một thuộc tính không tồn tại, đồng thời cũng làm cho mã của chúng ta trở nên ngắn gọn và dễ đọc hơn.
5. Sử dụng magic strings, magic numbers...
Magic numbers và magic strings là các con số hoặc chuỗi được sử dụng trực tiếp trong code mà thường không có ngữ cảnh rõ ràng nhưng lại có mục đích.
Việc tốt nhất là gán các giá trị này cho các hằng số, vì nếu không làm như vậy, chúng có thể trở nên khó hiểu và gây ra lỗi trong quá trình debug.
function calculateArea(radius) {
return 3.14 * radius * radius; // 3.14 là một magic number
}
Trong đoạn code trên, số 3.14 được gọi là magic number vì nó được sử dụng trực tiếp trong code mà không có một giải thích rõ ràng cho nó.
Chúng ta sửa lại như sau thì code sẽ clean hơn :D
const PI = 3.14;
function calculateArea(radius) {
return PI * radius * radius;
}
6. Truyền nhiều params vào function hay một single object chứa các params?
Việc truyền nhiều tham số hay truyền một đối tượng chứa nhiều tham số vào một hàm sẽ phụ thuộc vào từng trường hợp cụ thể và cách tiếp cận lập trình của từng người.
Tuy nhiên, khi truyền một đối tượng chứa nhiều params vào một hàm thì có thể giúp cho code dễ đọc hơn và dễ bảo trì hơn.
Ví dụ, việc truyền một đối tượng options
vào hàm render()
của một component có thể giúp cho việc hiểu rõ hơn những thiết lập khác nhau được truyền vào thành phần đó. Điều này thậm chí còn tốt hơn nếu chúng ta dùng kèm với Typescript.
Ngoài ra, việc truyền một object có thể giúp tránh tình trạng quên truyền params hoặc truyền params sai vị trí. Tuy nhiên, khi quá nhiều tham số được truyền vào một object, code có thể trở nên khó hiểu và khó bảo trì.
Do đó, cần cân nhắc và sử dụng phương pháp phù hợp với từng trường hợp cụ thể để có được code dễ đọc, dễ bảo trì và dễ mở rộng.
export interface IStartChargingSessionActionOptions {
driverId: string
chargerId: string
projection: IFieldMapProjection
input: IStartChargingSessionInput
}
@Injectable()
export class StartChargingSessionAction
implements DomainActionObservable<IStartChargingSessionActionOptions>
{
public constructor(private readonly _dataApiService: DataApiService) {}
public handle({
driverId,
input,
projection,
}: IStartChargingSessionActionOptions): Observable<ChargingSession> {
return scheduled(
this._dataApiService.mutation<{
chargingSessionCreate: ChargingSession
}>({
chargingSessionCreate: [
{
input: {
...input,
chargerId,
driverId,
timeStart: new Date(),
},
},
projection,
],
}),
asyncScheduler
).pipe(map(({ chargingSessionCreate }) => chargingSessionCreate))
}
}
7. Viết tắt khi check các giá trị falsy
Chúng ta đã từng gặp tình huống kiểm tra xem một biến có tồn tại hay không hoặc nó có chứa giá trị khác với null
hoặc undefined
không. Vì thế, chúng ta thường phải thực hiện kiểm tra rất dài như thế này:
if (x !== '' && x !== null && x !== undefined) {
// Do something
}
Có một cách viết đơn giản và trang nhã hơn như sau:
if (!!x) {
// Do something
}
Clean hơn nhiều đúng không :D
Túm cái váy lại
Viết code sạch luôn là trách nhiệm của chúng ta. Mình đã học được rằng việc có một code dễ bảo trì và dễ đọc sẽ tiết kiệm được rất nhiều giờ làm việc cho bạn và team của bạn.
Hãy nhớ rằng chúng ta dành nhiều thời gian để đọc code hơn là viết code. Mình hy vọng những mẹo nhỏ này sẽ giúp cho bạn tạo ra những sản phẩm tuyệt vời và kỳ diệu.