Bài viết gốc: https://www.jt1.vn/single-post/10-loi-de-mac-phai-khi-lam-viec-voi-java
Trong quá trình làm việc với Java, các nhà lập trình thường bỏ đi những chi tiết nhỏ. Nhưng những chi tiết nhỏ đó sẽ khiến các nhà lập trình hao tốn dung lượng bộ nhớ và không thể tối ưu hóa, bên cạnh đó còn làm giảm sự chuyên nghiệp trong quá trình làm việc. Ở bài viết này JT1 sẽ liệt kê ra các lỗi thường gặp để các bạn có thể theo dõi và loại bỏ những lỗi nhỏ đó nhé!
Dưới đây là 10 lỗi chung mà các nhà lập trình trên nền tảng Java thường mắc phải:
1. Bỏ qua các sửa đổi truy cập
2. Không sử dụng equals()
3. Nối chuỗi
4. Mật khẩu dưới dạng chuỗi
5. Trả về null
6. Truyền null
7. Phương thức nặng
8. Sử dụng các “mã trả lại” thay vì ngoại trừ
9. Chạm vào bộ sưu tập trong khi lặp
10. Sử dụng StringBuffer
Nào chúng ta cùng nhau tìm hiểu về lỗi đầu tiên!
1. Bỏ qua các sửa đổi truy cập
Chúng ta thường quên phạm vi của công cụ sửa đổi truy cập được bảo vệ trong Java. Chúng ta thường chỉ đề cập đến một trong hai vấn đề:
- Các trường, phương thức và hàm được bảo vệ có thể được truy cập từ cùng 1 gói
- Các trường, phương thức và hàm được bảo vệ có thể được truy cập từ các lớp con
Điều đó thật tệ, khi không trích dẫn phạm vi mức gói. Vì điều đó chỉ ra rằng bạn chưa bao giờ thử nghiệm một phương thức được bảo vệ( các gói bảo vệ có thể được truy cập từ đường dẫn lớp thử nghiệm, miễn là gói đó giống nhau). Các phương thức công khai và được bảo vệ là một phần của API mà phần mềm của bạn cung cấp.
Khi bạn quên đề cập thì tương đương với lời tuyên bố bạn sẽ chẳng bao giờ có bài viết ý nghĩa cho phần mềm của bạn.
2. Không sử dụng equals()
Nếu bạn sử dụng == thay vì lệnh equals(), kết quả bất ngờ sẽ khiến bạn thay đổi thói quen sử dụng chúng.
Giải thích:
Bạn không bao giờ sử dụng == khi so sánh 2 chuỗi( string) và chung hầu hết các đối tượng. == chỉ so sánh tham chiếu của 2 toán mạng( so sánh địa chỉ bộ nhớ chứ không phải nội dung của chúng.
Trong ví dụ trên, chuỗi y không có lợi cho chuỗi Interning: địa chỉ bộ nhớ của nó khác với địa chỉ của x.
3. Nối chuỗi
Nếu bạn làm việc với một số lượng lớn chuỗi hoặc một chuỗi lớn, bạn có thể lãng phí rất nhiều bộ nhớ trong quá trình nối.
Trong ví dụ trên, chúng ta đang tạo một vài đối tượng stringbuilder và string: 10.000.000 stringbuilder và 10.000.001 string, một cách chính xác!
Giải thích:
Để dễ hiểu chúng ta lùi lại một bước, khi bạn sử dụng phép toán + cho nối chuỗi, bạn đang tạo đối tượng trung gian lưu trữ kết quả của phép nối trước khi gán giá trị của nó cho đối tượng đích.
Trong ví dụ trên, chúng ta đã tạo tổng cộng 3 đối tượng: 2 cho chữ và 1 cho phép nối. Đó là bảng sao của kết quả chuỗi đầu tiên cộng với” world”. Điều này xảy xa bởi vì chuỗi là bất biến.
Nhưng trình biên dịch đủ thông minh để chuyển đổi mã thành như sau( không áp dụng cho Java 9+, bởi vì nó sử dụng StringContactFacotry nhưng kết quả khá giống nhau)
Việc tối ưu hóa này loại bỏ đối tượng trung gian và bộ nhớ bị chiếm bởi 2 chuỗi ký tự và stringbuilder. Nói chung, số lượng đối tượng Chuỗi giảm từ O (n²) xuống O (n). Quay lại ví dụ đầu tiên, trình biên dịch tối ưu hóa mã như thế này.
Trình biên dịch chỉ tối ưu hóa kết nối bên trong, nhưng điều này đang tạo ra rất nhiều đối tượng StringBuilder và String! Cách nối chuỗi đúng là như sau và chỉ cần 1 StringBuilder và 1 String.
Không khó để củng cố lại đúng không nào!
4. Mật khẩu dưới dạng chuỗi
Lưu trữ mật khẩu được cung cấp bởi người dùng trong đối tượng String là một vấn đề bảo mật, vì chúng dễ bị tấn công bộ nhớ.
Bạn nên sử dụng char[], như là Jpasswordfield và PasswordJ4 đã làm.
Nếu chúng ta đang nói về các ứng dụng web, hầu hết các bộ chứa web đều chuyển mật khẩu văn bản đơn giản trong đối tượng HttpServletRequest dưới dạng chuỗi, vì vậy trong trường hợp này, có hầu như bạn không thể thao tác gì về nó.
Giải thích:
Các chuỗi được lưu trữ bởi JVM( interning) và được lưu trữ trong không gian PermGen( trước Java 8) hoặc trong không gian Heap. Trong cả hai trường hợp, các giá trị lưu trữ chỉ bị xóa sau khi bộ sưu tập rác xảy ra: điều này có nghĩa là bạn sẽ không biết khi nào một giá trị cụ thể bị xóa khỏi chuỗi, do bộ sưu tập rác hoạt động không xác định.
Một vấn đề khác là những chuỗi này là bất biến vì vậy bạn không thể xóa chúng. Tuy nhiên char[] không phải bất biến và có thể xóa( ví dụ: thay thế từng phần tử phần 0) sau khi xử lý nó. Với thủ thuật đơn giản này, kẻ tấn công sẽ chỉ tìm thấy các mảng bằng 0 trong bộ nhớ thay vì mật khẩu văn bản đơn giản.
Xử lý tốt bước này thì độ bảo mật sẽ tăng cao lắm nha!
5. Returning null( Trả về null)
Vấn đề của việc trả về null là bạn đang buộc người gọi kiểm tra null về kết quả. Trong trường hợp này người gọi được mong đợi rằng nếu không có mục nào thì một danh sách trống sẽ được trả về.
Bạn luôn muốn trả về một ngoại lệ hoặc một đối tượng đặc biệt( như là danh sách trống) hoặc ứng dụng sử dụng mã của bạn sẽ bị ảnh hưởng bởi NullPulumExceptions.
Chưa hết đâu nhé, chúng ta hãy cùng xem xét 5 lỗi còn lại khi làm việc với Java.
6. Truyền null
Ở một khía cạnh khác, truyền null có nghĩa là bạn đang chấp nhận rằng mã bạn đang gọi có thể quản lý null. Nếu nó không đúng ứng dụng của bạn sẽ đưa ra một NullPulumException chắc chắn.
Ngoài ra, bạn đang tạo ra rất nhiều nhầm lẫn trong mã của bạn khi bạn thông qua null rõ ràng. Sau đây là một ví dụ cổ điển:
Khi init() được gọi, không có user người dùng nào sẵn có. Vậy tại sao một phương thức cái thực hiện với user nếu bạn thậm chí không có một user đơn? Nếu bạn cần logic chứa trong grantaccesstouser bạn nên trích xuất nó theo một phương thức khác và sử dụng nó thay vì truyền null.
7. Phương pháp nặng
Ví dụ sau có lẽ là nguyên nhân dẫn đến mất hiệu suất trong hệ thống của bạn:
Pattern.compile là phương pháp nặng và không nên gọi nó mỗi khi bạn cần kiểm tra xem một chuỗi có khớp với cùng một mẫu không.
Giải thích:
Pattern.compile biên dịch trước mẫu để sử dụng biểu diễn trong bộ nhớ nhanh hơn. Hoạt động này đòi hỏi lượng lớn năng lượng máy tính. Một cách cổ điển để đạt được hiệu suất là lưu trữ đối tượng mẫu trong trường tĩnh như thế này:
Giải pháp này nên được sử dụng mỗi khi bạn sử dụng lại cùng đối tượng tính toán nặng.
8. Sử dụng” mã trả lại” thay vì đưa ra ngoại lệ
Theo một cách nào đó, các ngoại lệ được các nhà phát triển cảm thấy là xấu xa và họ có xu hướng viết các phương thức trả về các giá trị lạ thay vào đó, như -1 hoặc “C_ERR”.
Đây là trường hợp điển hình đáng để tạo một ngoại lệ tùy chỉnh. Ví dụ có thể được viết lại như thế này:
Bạn có thể thấy khả năng đọc và bảo trì tăng lên rất nhiều. Người gọi không phải xử lý từng mã trả về đơn lẻ mà chỉ cần đọc nội dung của DeviceStartException.
9. Chạm vào bộ sưu tập trong khi lặp
Mã này sẽ đưa ra concExModificationException.
Giải thích:
Xóa một mục khỏi danh sách trong khi lặp trên đó làm cho trình lặp danh sách đó hoạt động kém, ví dụ: bằng cách bỏ qua các phần tử, lặp lại các phần tử, lập chỉ mục ở phần cuối của mảng, v.v … Đó là lý do tại sao nhiều bộ sưu tập thích ném một concExModificationException.
Bạn nên sử dụng iterator của mảng bên dưới thay vì:
10. Sử dụng StringBuffer
Ví dụ này tạo ra rất nhiều chi phí do tính chất đồng bộ của Stringbuffer. Trong những tình huống phức tạp hơn, bạn có thể được dẫn dắt để tin rằng đồng bộ hóa thực sự là cần thiết.
Nếu bạn tìm thấy Stringbuffer trong một dự án thì đó có thể là do nó cần bởi một số API kế thừa (cụ thể là trước Java 5) và rất hiếm khi mã của bạn đang cố nối thêm chuỗi trong ngữ cảnh đồng thời.
Thay vào đó sử dụng Stringbuilder: được giới thiệu với Java 5 và tất cả hoạt động không được đồng bộ hóa.
Bài viết gốc: https://www.jt1.vn/single-post/10-loi-de-mac-phai-khi-lam-viec-voi-java