Java.next: Ngôn ngữ Java.next

Bài viết này mở ra một loạt bài m ới của Neal Ford trên developerWorks tiến hành so sánh kỹ 3 thế hệ tiếp theo của ngôn ngữ JVM: Groovy, Scala, and Clojure. Trong bài này, bạn sẽ tìm hiểu những điểm tương đồng và khác biệt giữa chúng — và bạn sẽ lựa chọn Java™ là ngôn ngữ lập trình chính của bạn hay không

pdf15 trang | Chia sẻ: lylyngoc | Lượt xem: 1714 | Lượt tải: 1download
Bạn đang xem nội dung tài liệu Java.next: Ngôn ngữ Java.next, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
Java.next: Ngôn ngữ Java.next Bài viết này mở ra một loạt bài mới của Neal Ford trên developerWorks tiến hành so sánh kỹ 3 thế hệ tiếp theo của ngôn ngữ JVM: Groovy, Scala, and Clojure. Trong bài này, bạn sẽ tìm hiểu những điểm tương đồng và khác biệt giữa chúng — và bạn sẽ lựa chọn Java™ là ngôn ngữ lập trình chính của bạn hay không Trong một bài phát biểu mà tôi có dịp từng hợp tác với Martin Fowler, ông đã có cái nhìn sâu sắc: Di sản của Java chính là nền tảng, không phải ngôn ngữ. Các kỹ sư Java đã có một quyết định tuyệt vời là tách ngôn ngữ ra khỏi thành phần runtime, tạo điều kiện cho hơn 200 ngôn ngữ có thể chạy được trên nền tảng Java. Kiến trúc này rất quan trọng cho các nền tảng lâu dài, bởi vì ngôn ngữ lập trình máy tính thường có tuổi thọ ngắn. Từ năm 2008, Hội nghị thượng đỉnh Ngôn ngữ JVM hàng năm, được tổ chức bởi Oracle, đã tạo cơ hội cho các nhà phát triển ngôn ngữ khác cởi mở hợp tác với các kỹ sư Java. Chào mừng bạn đến với loại bài về Java.next. Trong đó, tôi sẽ giới thiệu sơ bộ về ba ngôn ngữ JVM hiện đại — Groovy, Scala, và Clojure — chúng cung cấp một sự pha trộn thú vị của mô hình, lựa chọn thiết kế, và yếu tố tiện nghi. Tôi sẽ không dành nhiều thời gian ở đây để cung cấp sự mô tả sâu của mỗi ngôn ngữ, chúng đã có sẵn trên trang web của chúng tôi (xem phần Tài nguyên). Nhưng các trang web ngôn ngữ cộng đồng — với mục đích chính là truyền bá — thông tin khách quan hoặc thiếu các ví dụ về việc một ngôn ngữ là không thích hợp. So sánh nội dung tôi sẽ thực hiện trong loạt bài này là để giúp lấp đầy khoảng trống. Điều này đặt ra giai đoạn với tổng quan về ngôn ngữ Java.next và những lợi ích của việc học về chúng. Nhìn lại Java Ngôn ngữ Java nổi lên thông qua Bruce Tate, trong cuốn sách của ông Beyond Java (xem phần Tài nguyên), có thể gọi là một cơn bão hoàn hảo: sự kết hợp các yếu tố của sự nổi lên của web, không phù hợp của công nghệ web đã có vì nhiều lý do, và sự gia tăng của phát triển ứng dụng đa tầng của các doanh nghiệp. Tate cũng lập luận rằng cơn bão hoàn hảo này là một loạt các sự kiện độc đáo, và không có ngôn ngữ nào khác sẽ vượt qua sự nổi bật tương tự bằng cùng một cách. Ngôn ngữ Java đã chứng tỏ tính mềm dẻo, nhưng cú pháp của nó và mô hình vốn có những hạn chế phải mất nhiều thời gian mới hiểu rõ được. Mặc dù ngôn ngữ cũng đang có những thay đổi đầy hứa hẹn, nhưng cú pháp đơn giản không thể hỗ trợ các mục tiêu quan trọng trong tương lai, chẳng hạn như các yếu tố của lập trình chức năng. Nhưng nếu bạn đang cố gắng để tìm một ngôn ngữ mới duy nhất để thay Java, thì bạn đang tìm không đúng hướng. Lập trình đa ngôn ngữ (Polyglot programming) Lập trình đa ngôn ngữ— một thuật ngữ mà tôi đã hồi sinh và làm cho nó phổ biến lại trong một blog vào năm 2006 (xem phần Tài nguyên) — được dựa trên nhận thức rằng không có ngôn ngữ đơn thuần nào phù hợp để giải quyết mọi vấn đề. Một số ngôn ngữ đã được xây dựng trong quan điểm hoặc các tính năng phù hợp với các vấn đề cụ thể hơn. Ví dụ, như tính tinh vi của Swing, các nhà phát triển nhận ra rằng thật phức tạp khi viết UIS Swing trong Java, bởi vì nó đòi hỏi phải khai báo kiểu, lớp ẩn danh bên trong vụng về để thực hiện hành động, và các thủ tục khác. Sử dụng ngôn ngữ đó là tốt hơn phù hợp để xây dựng các UI, chẳng hạn một Groovy với SwingBuilder làm cơ sở (xem Tài nguyên), làm cho việc xây dựng các ứng dụng Swing dễ dàng hơn. Sự gia tăng của ngôn ngữ chạy trên JVM làm cho ý tưởng của lập trình đa ngôn ngữ hấp dẫn hơn, bởi vì bạn có thể trộn và kết hợp trong khi duy trì các mã byte cơ bản và thư viện giống nhau. Ví dụ, SwingBuilder không thể thay thế Swing, nó là lớp sẵn có trên Swing API. Tất nhiên, trong một thời gian dài, các nhà phát triển đã tiến hành pha trộn ngôn ngữ bên ngoài JVM — ví dụ, sử dụng SQL và JavaScript cho các mục đích chuyên môn — nhưng nó đang trở thành phổ biến trong phạm vi JVM. Nhiều dự án ThoughtWorks kết hợp nhiều ngôn ngữ, và tất cả các công cụ được phát triển bởi ThoughtWorks Studios sử dụng ngôn ngữ hỗn hợp. Ngay cả khi Java vẫn phát triển ngôn ngữ chính của bạn, học cách làm việc với ngôn ngữ thay thế cho phép bạn kết hợp chúng một cách chiến lược. Java sẽ vẫn là một phần quan trọng của hệ sinh thái JVM, nhưng cuối cùng hơn như ngôn ngữ lắp ráp của nền tảng — một nơi mà bạn đi hoàn toàn vì lý do hiệu suất hoặc để đáp ứng yêu cầu chuyên môn. Sự phát triển Trong đầu những năm 1980, khi tôi còn trong trường đại học, chúng tôi sử dụng một môi trường phát triển được gọi là Pecan Pascal. Tính năng độc đáo của nó là giống như mã Pascal có thể chạy trên cả máy Apple II hoặc IBM PC. Các kỹ sư Pecan đạt được điều này bằng cách sử dụng một cái gì đó bí ẩn được gọi là "byte code." Các nhà phát triển biên dịch mã Pascal của họ thành "byte code," chạy trên "máy ảo" được viết dành riêng cho mỗi nền tảng. Đó là một kinh nghiệm xương máu! Mã kết quả đã được chậm chạm kinh khủng ngay cả đối với bài toán đơn giản. Phần cứng vào thời điểm đó không phù hợp với sự thử thách này. Một thập kỷ sau khi Pecan Pascal, Sun phát hành Java bằng cách sử dụng kiến trúc tương tự, căng thẳng nhưng thành công trong môi trường phần cứng giữa những năm 1990. Nó cũng có thêm tính năng phát triển thân thiện khác như dọn dẹp bộ nhớ tự động. Đã từng làm việc với các ngôn ngữ như C++, tôi không bao giờ muốn lập trình với một ngôn ngữ không tự thu gom rác (non-garbage-collected) lần nào nữa. Tôi muốn dành nhiều thời gian ở một mức độ trừu tượng cao hơn suy nghĩ về cách để giải quyết vấn đề kinh doanh phức tạp, chứ không phải các vấn đề hệ thống phức tạp như việc quản lý bộ nhớ. Một trong những lý do mà ngôn ngữ máy tính thường không có vòng đời lâu dài là do tốc độ đổi mới trong ngôn ngữ và nền tảng thiết kế. Các nền tảng của chúng ta trở nên mạnh hơn, chúng có thể xử lý nhiều công việc phức tạp hơn. Ví dụ, tính năng memoization của Groovy (được bổ sung vào năm 2010) lưu trữ các kết quả của các lời gọi chức năng. Hơn là việc bạn phải viết tay để xử lý bộ đệm, điều này sẽ dễ gây ra lỗi, giờ đây bạn chỉ việc gọi hàm memoize(), như trong Liệt kê 1: Liệt kê 1. Hàm chức năng xử lý bộ nhớ trong Groovy def static sum = { number -> factorsOf(number).inject(0, {i, j -> i + j}) } def static sumOfFactors = sum.memoize() Trong Liệt kê 1, các kết quả từ hàm sumOfFactors sẽ được tự động lưu trữ. Bạn cũng có thể tùy chỉnh các hành vi của bộ nhớ đệm với các phương pháp thay thế, memoizeAtLeast() và memoizeAtMost(). Clojure cũng bao gồm memoization, và nó không quan trọng để thực hiện trong Scala. Tính năng tiên tiến như memoization tồn tại trong ngôn ngữ thế hệ tiếp theo (và trong một số khuôn khổ Java) sẽ dần dần tìm đường vào ngôn ngữ Java. Phiên bản tiếp theo của Java sẽ thêm chức năng bậc cao, làm cho memoization dễ dàng hơn nhiều để thực hiện. Bằng cách nghiên cứu thế hệ tiếp theo ngôn ngữ Java, bạn nhận được một sneak đỉnh cao vào các tính năng Java tương lai. Groovy, Scala, Clojure Groovy là cú pháp Java ở thế kỉ 21 — được ví như tách cà phê espresso thay vì cà phê bình thường. Mục tiêu thiết kế của Groovy là để cập nhật và loại bỏ ma sát từ cú pháp của Java trong khi hỗ trợ các mô hình chính trong ngôn ngữ Java. Vì vậy, Groovy "biết" về những thứ như JavaBeans, đơn giản hóa truy cập tài nguyên. Groovy kết hợp các tính năng mới với tốc độ nhanh, trong đó có một số tính năng chức năng quan trọng tôi sẽ nổi bật trong các phần sau. Groovy chủ yếu là một ngôn ngữ hướng đối tượng. Hai khác biệt cơ bản giữa Java và Groovy là "động" lúc nào cũng hơn "tĩnh" , và khả năng lập trình meta của nó tốt hơn đáng kể. Scala là một ngôn ngữ thiết kế từ nền lên để tận dụng lợi thế của JVM, nhưng với một cú pháp được thiết kế lại hoàn toàn. Scala là một ngôn ngữ tĩnh mạnh mẽ — nghiêm ngặt hơn so với Java nhưng lại không gây nhiều điều khó chịu — hỗ trợ cả hai mô hình lập trình hướng đối tượng và hướng chức năng, các ưu thế sau này. Ví dụ, Scala thích dùng val để khai báo, năng suất một biến không thay đổi (tương tự như đánh dấu một tham số final trong Java) thành các var, tạo ra nhiều quen thuộc có thể thay đổi các biến. Scala cung cấp một cầu nối từ những gì bạn có thể là (một lập trình hướng đối tượng bắt buộc) với những gì bạn có thể nên được (một lập trình thêm chức năng) bằng cách hỗ trợ cả hai mô hình sâu sắc. Clojure sử dụng cú pháp triệt để nhất từ các ngôn ngữ khác, là một phương ngữ của Lisp. Là một ngôn ngữ động mạnh mẽ (giống như Groovy), nó phản ánh các quyết định thiết kế cá tính. Mặc dù Clojure cho phép bạn khả năng tương tác đầy đủ và sâu sắc với di sản Java, tuy nhiên nó không cố gắng xây dựng một cầu nối từ mô hình cũ. Ví dụ, Clojure là chức năng và hỗ trợ định hướng đối tượng để cho phép khả năng tương tác. Tuy nhiên, nó hỗ trợ tất cả các tính năng mà quen với các lập trình viên lập trình hướng đối tượng, chẳng hạn như đa hình — nhưng trong một kiểu như chức năng chứ không phải là hướng đối tượng. Clojure được thiết kế xung quanh một vài nguyên tắc kỹ thuật cốt lõi, chẳng hạn như Software Transactional Memory, điều đó phá vỡ mô hình cũ có lợi cho khả năng mới. Mô hình Bên ngoài của cú pháp, sự khác biệt thú vị nhất trong số những ngôn ngữ quan tâm typing và mô hình chính cơ bản: chức năng hoặc bắt buộc. Động và tĩnh Tính Tĩnh trong ngôn ngữ lập trình dùng để khai báo kiểu rõ ràng, chẳng hạn như khai báo int x; Java. Tính Động đề cập đến ngôn ngữ mà không yêu cầu loại thông tin khai báo. Tất cả các ngôn ngữ đang được xem xét là ngôn ngữ mạnh mẽ, có nghĩa là code của bạn có thể phản ánh các loại sau khi chuyển đổi. Hệ thống kiểu Java đã bị chỉ trích rộng rãi như cung cấp rất nhiều các bất tiện của typing tĩnh mà không có đủ những lợi ích. Ví dụ, trước khi loại suy luận hạn chế hiện có, Java yêu cầu các nhà phát triển để khai báo kiểu lặp lại trên cả hai mặt của các bài tập. Scala là tĩnh typing hơn so với Java nhưng ít hơn nhiều phức tạp trong sử dụng hàng ngày, bởi vì nó làm cho sử dụng nhiều loại suy luận. Groovy có một hành vi mà ở cái nhìn đầu tiên dường như thu hẹp khoảng cách tĩnh vs năng động. Xem xét ví dụ đơn giản về collection factory được thể hiện trong Liệt kê 2: Liệt kê 2. Bộ sưu tập Groovy class CollectionFactory { def List getCollection(description) { if (description == "Array-like") new ArrayList() else if (description == "Stack-like") new Stack() } } Lớp ở Liệt kê 2 hoạt động như một factory, trả lại một trong hai ứng dụng giao diện List— ArrayList hoặc Stack — dựa trên mô tả description của đối số. Đối với các nhà phát triển Java, điều này xuất hiện để đảm bảo rằng bất cứ điều gì được trả về đáp ứng tương đồng đó. Tuy nhiên, hai tình huống thử nghiệm trong Liệt kê 3 thể hiện điều đó: Liệt kê 3. Các mẫu kiểm thử trên Groovy @Test void test_search() { List l = f.getCollection("Stack-like") assertTrue l instanceof java.util.Stack l.push("foo") assertThat l.size(), is(1) def r = l.search("foo") } @Test(expected=groovy.lang.MissingMethodException.class) void verify_that_typing_does_not_help() { List l = f.getCollection("Array-like") assertTrue l instanceof java.util.ArrayList l.add("foo") assertThat l.size(), is(1) def r = l.search("foo") } Trong các bài kiểm tra đơn vị đầu tiên trong Liệt kê 3, tôi thấy một Stack thông qua các factory, xác minh rằng nó thực sự là một Stack, sau đó thực hiện các hoạt động giống Stack như push(), size(), và search(). Tuy nhiên, trong các đơn vị kiểm tra thứ hai, tôi phải bảo vệ các thử nghiệm với một biệt lệ MissingMethodException để vượt qua quá trình thử nghiệm. Khi tôi lấy các bộ collection giống Array vào các biến như List, tôi có thể xác nhận rằng tôi có thể nhận về một danh sách trả về. Tuy nhiên, khi tôi thử gọi phương thức search(), nó sẽ xuất ra một biệt lệ vì ArrayList không bao gồm phương thức search(). Do đó, việc kê khai cung cấp không bảo vệ thời gian biên dịch đảm bảo rằng phương pháp gọi là đúng. Mặc dù việc này có vẻ như là một lỗi, nhưng đó là hành vi thích hợp. Các loại trong Groovy chỉ đảm bảo hiệu lực của lệnh assignment. Ví dụ, trong Liệt kê 3, nếu tôi trả về cái gì đó đã không thực hiện bởi giao diện List, thì một biệt lệ (GroovyCastException) sẽ được kích hoạt khi chạy. Điều này làm cho Groovy vững chắc và mạnh mẽ, gia đình typing động với Clojure. Tuy nhiên, những thay đổi gần đây với ngôn ngữ đã thực hiện các phân chia tĩnh vs động trong Groovy thậm chí không rõ ràng. Groovy 2.0 bổ sung thêm một chú thích @TypeChecked, cho phép bạn có những quyết định về sự nghiêm khắc của các loại kiểm tra, tại các lớp (class) hoặc phương pháp. Liệt kê 4 minh họa chú thích này: Liệt kê 4. Kiểm tra loại thông qua chú thích @TypeChecked @Test void type_checking() { def f = new CollectionFactory() List l = f.getCollection("Stack-like") l.add("foo") def r = l.pop() assertEquals r, "foo" } Trong Liệt kê 4, tôi bổ sung thêm chú thích @TypeChecked, giúp xác minh cả hai nhiệm vụ và phương thức gọi tiếp theo. Ví dụ, đoạn mã trong Liệt kê 5 sẽ không được biên dịch nữa: Liệt kê 5. Typing kiểm tra ngăn chặn phương pháp gọi không hợp lệ @TypeChecked @Test void invalid_type() { def f = new CollectionFactory() Stack s = (Stack) f.getCollection("Stack-like") s.add("foo") def result = s.search("foo") } Trong Liệt kê 5, tôi phải bổ sung thêm ép kiểu cho sự trả về từ các factory để cho phép tôi gọi phương thức search() của Stack. Thiết bị này đi kèm với những hạn chế: Nhiều tính năng động Groovy sẽ không hoạt động khi typing tĩnh được kích hoạt. Tuy nhiên, ví dụ này cho thấy sự phát triển liên tục của Groovy trong cầu nối khoảng cách tĩnh vs năng động. Tất cả các ngôn ngữ có cơ sở lập trình meta khá mạnh mẽ, typing nghiêm ngặt hơn có thể được thêm vào sau đó. Ví dụ, một số dự án bên tồn tại để thêm typing chọn lọc để Clojure. Nói chung, tuy nhiên, nếu typing lựa chọn là tùy chọn, nó không phải là một phần của hệ thống loại, đó là một cơ chế xác minh. Mệnh lệnh so với chức năng So sánh các thông tin chính giữa mệnh lệnh và chức năng. Lập trình theo Imperative (mệnh lệnh) tập trung vào việc hướng dẫn từng bước, trong nhiều trường hợp bắt buộc phần cứng cổ điển ở cấp thấp. Còn lập trình Functional (chức năng) tập trung nhiều hơn vào các chức năng như cấu trúc của lớp đầu tiên và cố gắng giảm thiểu quá trình chuyển đổi trạng thái và đột biến. Groovy, được lấy cảm hứng mạnh mẽ từ Java, chủ yếu là một ngôn ngữ mệnh lệnh. hưng ngay từ đầu, nhiều tính năng ngôn ngữ chức năng đã được bao gồm, và nhiều hơn nữa đã được thêm vào theo thời gian. Scala bắc cầu giữa hai mô hình này, hỗ trợ cả hai. Trong khi bạn thích (và được khuyến khích) lập trình theo hướng chức năng, thì Scala vẫn hỗ trợ lập trình hướng đối tượng và hướng mệnh lệnh. Vì vậy, sử dụng Scala đúng đòi hỏi một đội xử lý kỷ luật để đảm bảo rằng bạn không trộn và kết hợp mô hình bừa bãi, điều này luôn là một mối nguy hiểm trong ngôn ngữ đa mô hình. Clojure thì không bộc lộ các tính năng. Nó hỗ trợ hướng đối tượng cho phép dễ dàng tương tác với các ngôn ngữ JVM, nhưng nó cố gắng để không trở thành cầu nối. Thay vào đó, thiết kế của Clojure giúp khẳng định những thực hành kỹ thuật tốt mà các nhà thiết kế có thể nghĩ tới. Những quyết định có ảnh hưởng sâu rộng, cho phép Clojure để giải quyết một số vấn đề dai dẳng trong thế giới Java (chẳng hạn như vấn đề xử lý đồng thời) theo các cách đột phá. Nhiều người trong số đó thay đổi trong suy nghĩ cần thiết để tìm hiểu các ngôn ngữ mới sẽ đến từ sự phân chia imperative/functional, và đó là một trong những khu vực có giá trị nhất của việc tìm hiểu loạt bài này. Kết luận Các nhà phát triển đang sống trong một thế giới ngày càng nhiều ngôn ngữ nơi mà đa ngôn ngữ được sử dụng để giải quyết vấn đề. Học tập để tận dụng các ngôn ngữ mới một cách hiệu quả giúp bạn xác định khi khi nào phương pháp này là thích hợp. Thậm chí nếu bạn không rời bỏ Java, nó sẽ dần dần kết hợp các tính năng từ các ngôn ngữ JVM thế hệ tiếp theo; nhìn vào chúng có thể sẽ cho bạn thấy trước được tương lai của ngôn ngữ Java. Trong phần tiếp theo của Java.next, tôi sẽ bắt đầu so sánh bằng cách nhìn vào những điểm chung của Groovy, Scala, và Clojure.