Header Ads

Nhập môn lập trình Java


Tóm tắt:  Ngôn ngữ Java, và nền tảng Java luôn phát triển như một cuộc cách mạng trong lập trình. Mục tiêu của bài hướng dẫn này là giới thiệu cho bạn cú pháp của Java mà bạn hầu như chắc chắn sẽ gặp trên con đường nghề nghiệp và cho bạn thấy những thành tố đặc thù (idioms) của nó giúp bạn tránh khỏi những rắc rối. Theo bước Roy Miller, chuyên gia Java khi ông hướng dẫn bạn những điểm cốt yếu của lập trình Java, bao gồm mô hình hướng đối tượng (OOP) và cách thức áp dụng nó vào lập trình Java; cú pháp của ngôn ngữ Java và cách sử dụng; tạo ra đối tượng và thêm các hành vi, làm việc với các sưu tập (collections), xử lý lỗi; các mẹo để viết mã lệnh tốt hơn


Viết mã lệnh Java tốt
Bây giờ bạn đã biết khá nhiều về cú pháp của Java, nhưng đó chưa phải là lập trình thực sự chuyên nghiệp. Vậy điều gì tạo nên một chương trình Java “tốt”?
Có lẽ số lượng câu trả lời cho câu hỏi này cũng nhiều như số các lập trình viên Java chuyên nghiệp. Nhưng tôi có một số đề xuất mà tôi tin rằng hầu hết các lập trình viên Java chuyên nghiệp sẽ đồng ý cải tiến chất lượng của mã lệnh Java mà họ xử lý hàng ngày. Với chủ tâm bộc lộ hết, tôi phải nói rằng tôi nghiêng về ủng hộ các phương pháp linh hoạt (agile) như Lập trình đỉnh cao (Extreme Programming - XP), do đó nhiều quan điểm của tôi về mã lệnh “tốt” nói chung là đã được cộng đồng phát triển theo phương pháp linh hoạt và đặc biệt là XP công bố. Tôi nghĩ hầu hết các lập trình viên Java chuyên nghiệp giàu kinh nghiệm sẽ đồng ý với những điểm mà tôi sẽ trình bày trong phần này.
Chúng ta xây dựng lớp Adult đơn giản trong tài liệu này. Thậm chí sau khi chúng ta đã chuyển phương thức main() sang lớp khác, Adult cũng còn hơn 100 dòng mã lệnh. Lớp này có tới hơn hai mươi phương thức, và nó thực sự không làm được gì nhiều nếu so sánh với nhiều lớp và bạn có thể đã thấy (hay tạo ra) trong hoạt động nghề nghiệp. Đây là một lớp nhỏ. Không có gì bất thường khi bạn thấy có những lớp có từ 50 đến 100 phương thức. Điều gì khiến cho bạn nghĩ rằng ít hơn là tệ hơn ? Chẳng có gì cả. Điều quan trọng về các phương thức là bạn có những gì bạn cần. Nếu bạn cần vài phương thức trợ giúp, về bản chất thực hiện cùng một việc nhưng nhận các tham số khác nhau (như phương thức addMoney() chẳng hạn), thì đó là một lựa chọn hay. Hãy đảm bảo là chỉ hạn chế trong danh sách các phương thức bạn cần và đừng thêm nữa.
Thông thường, một lớp có quá nhiều phương thức sẽ có một vài phương thức không thuộc danh sách này vì rằng đối tượng khổng lồ thì cũng làm quá nhiều thứ. Trong cuốn Tái cấu trúc (Refactoring, xem Các tài nguyên), Martin Fowler gọi điều này là có mùi mã phương thức ngoại lai (Foreign Method code smell). Nếu bạn có một đối tượng với 100 phương thức, bạn nên suy nghĩ kỹ về việc liệu đối tượng này có phải thực sự là nhiều đối tượng hay không. Trong trường học, các lớp đông sinh viên thường gây phiền toái. Điều này cũng xảy ra đối với mã lệnh Java.
Các phương thức nhỏ gọn cũng nên được ưu tiên hơn giống như các lớp nhỏ gọn với lý do tương tự.
Một trong những lời phàn nàn của các lập trình viên hướng đối tượng giàu kinh nghiệm đối với ngôn ngữ Java là nó cung cấp một đống hướng đối tượng nhưng không dạy họ cách thực hiện nó sao cho tốt. Nói cách khác, Java mang lại cho họ đủ rắc rối, dù ít nhất cũng không nhiều như ngôn ngữ C++. Nơi thường thấy điều này chính là trong một lớp với phương thức main() dài dằng dặc, hoặc chỉ một phương thức có tên là doIt(). Nếu chỉ vì bạn có thể nhồi tất cả mã lệnh của mình vào chỉ một phương thức trong một lớp thì điều đó không có nghĩa là bạn nên làm thế. Ngôn ngữ Java có nhiều gia vị cú pháp hơn nhiều ngôn ngữ hướng đối tượng khác nên cũng cần dài dòng đôi chút, nhưng không nên quá đà.
Hãy ngẫm nghĩ chốc lát về những phương thức cực kỳ dài ấy. Phải cuộn đến 10 trang màn hình đầy mã lệnh sẽ khiến cho bạn vô cùng khó khăn để hiểu cái gì đang xảy ra. Phương thức ấy làm gì? Bạn sẽ cần cả một cốc cà phê bự và nhiều giờ nghiên cứu để hiểu ra vấn đề. Phương thức nhỏ, thậm chí là tý hon thì cũng sẽ dễ dàng hơn. Hiệu suất chạy thi hành không phải là lý do để viết các phương thức nhỏ gọn. Khả năng dễ đọc hiểu mới là phần thưởng thực sự của nó. Điều này khiến cho mã lệnh của bạn dễ dàng bảo trì hơn và dễ thay đổi hơn khi bạn muốn thêm các tính năng mới.
Hãy hạn chế sao cho mỗi phương thức thực hiện chỉ một việc.
Mẫu viết mã lệnh hay nhất mà tôi đã từng xem qua (và tôi đã quên mất nguồn rồi) được gọi là các tên phương thức biểu lộ mục đích - intention-revealing method names. Trong hai tên phương thức dưới đây, cái nào dễ giải mãi hơn khi chỉ thoáng nhìn qua?
  • a()
  • computeCommission()
Câu trả lời thật hiển nhiên. Vì một vài lý do, các nhà lập trình dường như không thích đặt tên phương thức dài dòng. Tất nhiên một cái tên dài lố bịch có thể bất tiện, nhưng một cái tên dài đủ rõ ràng thì lại khác. Tôi chẳng gặp khó khăn gì với một cái tên phương thức như aReallyLongMethodNameThatIsAbsolutelyClear(). Vào lúc 3:00 giờ sáng khi tôi thử tìm hiểu xem tại sao chương trình của tôi không chạy, nếu tôi gặp phải một phương thức có tên là a() thì tôi chỉ muốn nện cho ai đó một trận.
Hãy dành thêm vài phút để chọn một cái tên gợi tả tốt nhất có thể, bạn hãy cân nhắc việc đặt tên cho các phương thức theo cách thức sao cho mã lệnh của bạn đọc ra giống như lời nói thông thường vậy, thậm chí nếu điều này có nghĩa là cần thêm các phương thức phụ trợ để làm được việc đó. Ví dụ, hãy xem xét việc thêm vào một phương thức phụ trợ khiến cho đoạn mã lệnh này thêm dễ đọc hơn:
if (myAdult.getWallet().isEmpty()) {
  do something 
}

Phương thức isEmpty() của ArrayList tự nó rất có ích, nhưng điều kiện logic trong câu lệnh if của chúng ta có thể được lợi từ phương thức hasMoney() của Adult như sau:
public boolean hasMoney() {
 return !getWallet().isEmpty();
}
    

Sau đây là câu lệnh if đọc giống lời nói thông thường hơn:
if (myAdult.hasMoney()) {
  do something 
}
    

Kỹ thuật này đơn giản và có lẽ là bình thường trong trường hợp này, nhưng nó lại rất hữu hiệu khi mã lệnh trở nên phức tạp hơn.
Một trong những nguyên tắc chủ đạo để có được thiết kế đơn giản trong lập trình đỉnh cao (XP) là đạt được mục tiêu với ít lớp nhất có thể, nhưng không ít hơn. Nếu bạn cần một lớp khác, chắc chắn là nên thêm nó vào. Nếu thêm lớp khác làm cho mã lệnh của bạn đơn giản hơn hay làm cho bạn diễn dịch ý định của mình dễ dàng hơn thì hãy cứ tiếp tục thêm lớp vào. Nhưng chẳng có lý do gì để thêm các lớp chỉ để có chúng mà thôi. Thường khi bắt đầu dự án bạn có ít lớp hơn là khi hoàn thành xong xuôi, dĩ nhiên, nhưng cũng thường thì dễ tái cấu trúc mã lệnh của bạn thành nhiều lớp hơn là tích hợp chúng lại. Nếu bạn có một lớp có rất nhiều phương thức, phân tích xem liệu có phải có một lớp khác mắc bẫy vào đây và đang đợi được tách ra hay không. Nếu có, hãy tạo ra một đối tượng mới.
Trong hầu hết các dự án Java của tôi, không ai ngại xây dựng các lớp nhưng chúng tôi cũng luôn cố gắng giảm số lượng các lớp mà không làm cho ý định của mình kém tường minh.
Tôi thường viết các chú thích thừa trong mã lệnh của mình. Đọc chúng hệt như đọc cả một cuốn sách. Bây giờ tôi đã khôn ngoan hơn chút ít.
Tất cả các chương trình học tập về khoa học máy tính, tất cả các cuốn sách dạy lập trình và rất nhiều lập trình viên tôi quen biết đều khuyên bạn chú thích cho các mã lệnh. Trong một vài trường hợp, các chú thích thật sự có ích. Nhưng trong một số trường hợp khác thì chính chúng lại làm cho việc bảo trì mã lệnh khó thêm. Thử nghĩ xem bạn phải làm gì khi bạn thay đổi mã lệnh. Có chú thích ở đấy không? Nếu có, tốt hơn là bạn phải thay đổi cả chú thích hoặc là nó sẽ trở nên lỗi thời kinh khủng, và thời gian trôi đi, thậm chí nó chả diễn tả gì về mã lệnh hiện tại cả. Theo kinh nghiệm của tôi thì nó chỉ tăng gấp đôi thời gian bảo trì của bạn mà thôi.
Quy tắc chủ đạo của tôi là: Nếu mã lệnh khó đọc và khó hiểu đến nỗi cần phải có chú thích thì tôi cần làm cho nó sáng sủa hơn đủ để không cần chú thích nữa. Có thể là nó quá dài hoặc làm quá nhiều việc. Nếu thế, phải làm cho nó trở nên đơn giản hơn. Có thể nó quá khó hiểu. Nếu thế, tôi sẽ bổ sung thêm các phương thức phụ trợ để làm nó dễ hiểu hơn. Thực tế, trong 3 năm lập trình Java với các thành viên trong đội, tôi có thể đếm số lượng chú thích tôi đã viết trên đầu ngón tay và ngón chân. Hãy làm mã lệnh của bạn sáng sủa hơn! Nếu bạn cần một bức tranh tổng thể về hệ thống, hoặc một thành phần cụ thể nào đó làm cái gì, hãy viết tài liệu ngắn gọn để mô tả nó.
Những chú thích dài dòng thường khó bảo trì, thường chúng không diễn giải ý định của bạn tốt bằng một phương thức được viết chuẩn, nhỏ gọn. Hơn nữa chú thích sẽ nhanh chóng trở nên lỗi thời. Đừng phụ thuộc quá nhiều vào các chú thích.
Viết mã lệnh theo phong cách gì thực sự là vấn đề về sự cần thiết và cái có thể chấp nhận được trong môi trường của bạn là gì. Tôi thậm chí không biết đến một phong cách mà tôi có thể gọi là “tiêu biểu”. Nó là chuyện sở thích cá nhân. Ví dụ, những dòng lệnh sau có thể làm tôi giật nảy mình cho đến khi tôi biến đổi đi:
public void myMethod()
{
 if (this.a == this.b)
 {
   statements 
 }
}
    

Vì sao điều đó lại làm tôi băn khoăn? Vì cá nhân tôi không ưa kiểu viết mã lệnh thêm cả loạt dòng mới, mà theo ý kiến của tôi, không cần thiết. Trình biên dịch Java ghi nhận những dòng mã lệnh sau đây cũng giống như vậy, và tôi tiết kiệm được vài dòng:
public void myMethod() {
 if (this.a == this.b)
   statements 
}
    

Không có cách viết nào là “đúng” hay là “sai”. Chỉ đơn giản là nó ngắn hơn cách kia. Vậy điều gì xảy ra khi tôi viết mã lệnh với những người thích cách đầu tiên hơn ? Chúng tôi trao đổi về nó, chọn ra kiểu chúng tôi sẽ dùng và sau đó thì trung thành với nó. Chỉ có một quy tắc nghiêm ngặt là hãy nhất quán. Nếu những người tham gia thực hiện một dự án áp dụng các phong cách khác nhau, việc đọc mã lệnh sẽ khó khăn. Hãy chọn một kiểu thôi và đừng thay đổi nữa.
Một vài lập trình viên Java thích dùng lệnh switch. Tôi thì nghĩ lệnh đó cũng hay, nhưng sau đó nhận ra rằng switch thực ra chỉ là một loạt lệnh if, và như thế có nghĩa là logic điều kiện sẽ xuất hiện ở nhiều nơi trong mã lệnh của tôi. Đó là sự lặp lại mã lệnh, và đấy là cái không chấp nhận được. Tại sao? Bởi vì có những mã lệnh giống nhau tại nhiều vị trí có thể khiến cho mã lệnh khó thay đổi. Nếu tôi có cùng lệnh switch ở tại 3 nơi và tôi muốn thay đổi cách xử lý một trường hợp cụ thể thì tôi phải thay đổi 3 đoạn mã lệnh.
Bây giờ, nếu như bạn có thể tái cấu trúc mã lệnh sao cho chỉ còn có một lệnh switch thì sao? Tuyệt vời! Tôi không tin có bất kỳ chuyện tệ hại gì khi dùng nó. Trong một vài trường hợp, dùng switch lại khiến mã lệnh sáng tỏ hơn là nhiều lệnh if lồng nhau. Nhưng nếu bạn thấy nó xuất hiện ở nhiều nơi có vấn đề thì bạn nên sửa đi. Cách dễ nhất để khỏi vấp phải vấn đề này là tránh dùng lệnh switch trừ khi nó là công cụ tốt nhất cho công việc đó. Theo kinh nghiệm của tôi thì điều này rất hiếm khi xảy ra.
Tôi để dành lời khuyến cáo gây nhiều tranh cãi nhất đến phút cuối cùng. Hãy chuẩn bị tinh thần nào và hít thở thật sâu.
Tôi tin bạn sẽ sai lầm khi đặt toàn bộ các phương thức của bạn ở chế độ truy nhập là public. Các biến cá thể đặt ở chế độprotected.
Dĩ nhiên, nhiều lập trình viên chuyên nghiệp sẽ rùng mình khi nghĩ đến điều đó vì nếu mọi thứ đều công khai thì ai cũng có thể biến đổi chúng được, có thể bằng cả những cách bất hợp pháp. Trong một thế giới mà mọi thứ đều có thể truy cập công cộng, bạn phải phụ thuộc vào tính nguyên tắc của người lập trình trong việc đảm bảo rằng mọi người không thể truy nhập vào những thứ mà họ không nên truy nhập khi họ không được phép. Nhưng trong thực tế lập trình, ít có điều gì gây phiền toái hơn là muốn truy cập một biến hay một phương thức mà bạn không nhìn thấy được. Nếu bạn hạn chế truy cập những thứ trong mã lệnh và bạn coi rằng những người khác sẽ không truy cập được, thì có lẽ bạn thực sự là đấng toàn năng. Đó là một giả định nguy hiểm trong mọi thời điểm.
Nỗi phiền toái này thường lộ ra khi bạn dùng mã lệnh của người khác. Bạn có thể thấy một phương thức thực hiện chính xác những gì bạn muốn làm, nhưng nó lại không cho phép truy cập công cộng. Đôi khi có lý do chính đáng để làm việc đó và việc hạn chế các truy nhập là có ý nghĩa. Thế nhưng, đôi khi lý do duy nhất để chế độ truy cập không là public là những người viết mã lệnh đã nghĩ rằng “Chả bao giờ có ai cần truy nhập vào đây cả”. Hoặc có thể họ đã nghĩ “Chẳng ai sẽ cần truy nhập vì…” và tiếp theo, không có một lý do đủ vững chắc. Nhiều khi người ta sử dụng chế độ private vì nó có sẵn đó. Đừng làm vậy.
Hãy để các phương thức là public và các biến là protected cho đến khi bạn có lý do hợp lý để hạn chế truy nhập.
Bây giờ bạn đã biết cách làm thế nào để viết mã lệnh Java tốt và giữ cho nó chuẩn mực.
Cuốn sách hay nhất trong ngành công nghiệp phần mềm là cuốn “Tái cấu trúc (Refactoring)” của Martin Fowler (xem Phần tài nguyên). Nó thậm chí còn rất hài hước nữa. Tái cấu trúc có nghĩa là thay đổi thiết kế của mã lệnh hiện có mà không làm biến đổi kết quả của nó. Fowler nói về các “mùi mã lệnh” đang xin được tái cấu trúc, và đi sâu vào chi tiết các kỹ thuật khác nhau (hoặc các kiểu tái cấu trúc khác nhau) để khắc phục chúng. Theo quan điểm của tôi, việc tái cấu trúc và khả năng viết mã lệnh kiểm thử đi trước (code test-first) (xem Phần tài nguyên) là những kỹ năng quan trọng nhất mà các lập trình viên mới vào nghề cần học. Nếu mọi người đã thạo cả hai, nó sẽ cách mạng hóa ngành công nghiệp này. Nếu bạn thành thạo ở cả hai kỹ năng, sẽ rất dễ kiếm việc làm, vì bạn sẽ có thể làm việc hiệu quả hơn so với đa số người tìm việc khác.
Việc viết mã lệnh Java tương đối đơn giản. Viết mã lệnh Java “tốt” lại là một bí quyết. Bạn hãy tập trung để trở thành những người lành nghề

No comments:

Powered by Blogger.