051.1 Bài 1
Chứng chỉ: |
Open Source Essentials |
---|---|
Phiên bản: |
1.0 |
Chủ đề: |
051 Những nguyên tắc cơ bản về Phần mềm |
Mục tiêu: |
051.1 Thành phần của Phần mềm |
Bài học: |
1 trên 1 |
Giới thiệu
Phần mềm mã nguồn mở và tự do — thường được viết tắt là FOSS (Free and Open Source Software) — đã trở thành một phần không thể thiếu trong cuộc sống hàng ngày dù chúng ta hầu như không hề nhận ra. Ví dụ: FOSS có thể đứng sau tất cả các hoạt động của chúng ta trên internet dưới một số hình thức: trên máy tính nơi chúng ta xem các trang web trong trình duyệt hoặc trên các máy chủ lưu trữ và phân phối các trang web này ngay khi chúng ta truy cập chúng.
Phần mềm là gì?
Tuy nhiên, trước khi tìm hiểu về toàn bộ các chi tiết cụ thể về phần mềm mã nguồn mở và tự do, chúng ta cần làm rõ phần mềm thực sự là gì. Hãy bắt đầu với một mô tả tổng quan: Phần mềm là bộ phận phi vật lý và phi vật chất của máy tính ở mọi hình thức. Phần mềm sẽ đảm bảo rằng các bộ phận vật lý (phần cứng) của máy tính có thể tương tác và máy tính có thể chấp nhận lệnh và thực thi các tác vụ.
Điện thoại thông minh, máy tính xách tay hay chính máy chủ trong trung tâm dữ liệu đều chỉ là những cỗ máy làm từ nhựa và kim loại khi tắt nguồn. Ngay sau khi được bật, phần mềm sẽ khởi động dưới dạng các chuỗi lệnh được mã hóa để điều khiển các thành phần riêng lẻ của máy và cho phép người dùng tương tác với máy cũng như thực hiện các tác vụ cụ thể bằng cách gọi các ứng dụng riêng biệt.
Công việc của nhà phát triển phần mềm là phân tích các tác vụ mà máy tính phải thực hiện và chỉ rõ các nhiệm vụ ấy theo một cách sao cho máy tính có thể thực hiện được. Những công cụ được các nhà phát triển sử dụng cũng nhiều và đa dạng không kém gì so với các tác vụ mà phần mềm phải thực hiện.
Một số tác vụ phần mềm được kết nối rất chặt chẽ với phần cứng và kiến trúc của máy tính (ví dụ: việc chỉ định và quản lý bộ nhớ hoặc xử lý các tiến trình khác nhau). Do đó, Lập trình viên hệ thống sẽ làm việc sát sao với phần cứng.
Mặt khác, các nhà phát triển ứng dụng sẽ tập trung nhiều hơn vào người dùng và các chương trình ứng dụng cho phép người dùng thực hiện các tác vụ của họ một cách hiệu quả và trực quan. Ví dụ về một ứng dụng phức tạp chính là một chương trình xử lý văn bản cung cấp tất cả các chức năng định dạng văn bản trong các bảng chọn hoặc nút bấm và cũng có thể hiển thị văn bản dưới dạng sẵn sàng để in.
Nói chung, thuật toán chính là một cách để giải quyết vấn đề. Ví dụ: để tính giá trị trung bình, thuật toán thông thường sẽ là cộng một tập hợp các giá trị và chia tổng cho tổng số giá trị. Theo truyền thống, các thuật toán sẽ được thiết kế và thực hiện bởi các lập trình viên; ngày này, các thuật toán cũng có thể được tạo ra bởi trí tuệ nhân tạo.
Các khái niệm trong chương này có thể giúp chúng ta hiểu được những điểm mạnh cũng như những rủi ro của FOSS và có thể đưa ra những lựa chọn sáng suốt hay thậm chí là quyết định xem bản thân chúng ta có muốn trở thành một nhà phát triển phần mềm hay không.
Ngôn ngữ Lập trình
Ngôn ngữ lập trình là những ngôn ngữ nhân tạo có cấu trúc chặt chẽ được sử dụng để giúp máy tính biết phải làm gì. Các chương trình thường được viết bằng văn bản, nhưng một số ngôn ngữ sẽ được viết dưới dạng đồ họa. Các nhà phát triển phần mềm sẽ viết các chỉ dẫn (được gọi là mã - code) cho máy tính bằng chính các ngôn ngữ nhân tạo này. Tuy nhiên, phần cứng của máy tính sẽ không trực tiếp thực thi mã này. Phần cứng chỉ có thể thực thi trực tiếp một chuỗi mẫu bit được lưu trong bộ nhớ được gọi là mã máy hoặc ngôn ngữ máy. Tất cả các ngôn ngữ lập trình đều sẽ được chuyển đổi thành mã máy bằng một trình biên dịch hoặc được thông dịch bởi một chương trình mã máy khác được gọi là trình thông dịch để phần cứng thực thi các chỉ dẫn này.
Một số ngôn ngữ lập trình được sử dụng rộng rãi nhất hiện nay gồm có Python, JavaScript, C, C++, Java, C#, Swift và PHP. Mỗi ngôn ngữ lập trình này đều có những điểm mạnh và điểm yếu riêng và việc lựa chọn ngôn ngữ sẽ tùy thuộc vào mỗi dự án cũng như nhu cầu của nhà phát triển. Ví dụ: Java là một lựa chọn phổ biến để phát triển các ứng dụng doanh nghiệp có quy mô lớn, trong khi Python thường được sử dụng để tính toán khoa học và phân tích dữ liệu.
Các nhà phát triển đã cho thấy khả năng sáng tạo đầy ấn tượng của mình trong việc thiết kế ngôn ngữ lập trình. Ban đầu, chúng chỉ là những ngôn ngữ cấp thấp tương tự như các chỉ dẫn trong máy tính. Các ngôn ngữ đã dần đạt đến bậc cao, có nghĩa là chúng có thể thể hiện các kết hợp mạnh mẽ giữa các chỉ dẫn bằng những thuật ngữ ngắn gọn. Một số ngôn ngữ sẽ phản ánh cách tư duy tự nhiên của con người trong khi vẫn duy trì được sự chặt chẽ cần thiết để hoạt động một cách chính xác.
Hiện có khoảng 400 ngôn ngữ lập trình đã được công nhận dù khá nhiều trong số đó chỉ được sử dụng trong các ứng dụng đặc thù hoặc ở trong các môi trường cũ. Mỗi ngôn ngữ đều được phát triển để giải quyết một số nhiệm vụ nhất định.
Đặc điểm, cú pháp và cấu trúc của Ngôn ngữ Lập trình
Việc lựa chọn ngôn ngữ lập trình có thể có ảnh hưởng một cách đáng kể đến hiệu suất, khả năng mở rộng và phát triển trong một dự án phần mềm. Những khía cạnh này thể hiện các yếu tố quan trọng của ngôn ngữ.
Đặc điểm của Ngôn ngữ Lập trình
Một số đặc điểm và tính chất chung của ngôn ngữ lập trình gồm có:
- Tính đồng thời
-
Tính đồng thời biểu thị việc xử lý đồng thời nhiều tác vụ bằng cách chạy trên các bộ xử lý phần cứng khác nhau hoặc bằng cách xen kẽ việc sử dụng một bộ xử lý duy nhất của các tác vụ. Mức độ đồng thời được hỗ trợ bởi một ngôn ngữ lập trình có thể ảnh hưởng lớn đến hiệu suất và khả năng mở rộng của nó, đặc biệt là đối với các ứng dụng yêu cầu xử lý thời gian thực hoặc có lượng dữ liệu lớn. Mỗi phần công việc riêng biệt có thể được gọi là một tiến trình (process), tác vụ (task) hoặc luồng (thread).
- Quản lý bộ nhớ
-
Quản lý bộ nhớ có nghĩa là phân bổ và giải phóng bộ nhớ trong một chương trình. Tùy thuộc vào ngôn ngữ hoặc môi trường thời gian chạy, việc quản lý bộ nhớ có thể được lập trình viên thực hiện thủ công hoặc được xử lý tự động. Quản lý bộ nhớ một cách phù hợp là yếu tố rất quan trọng để đảm bảo rằng chương trình có thể sử dụng bộ nhớ một cách hiệu quả và sẽ không bị hết bộ nhớ hoặc gây ra các sự cố khác. Nếu một chương trình không thể giải phóng bộ nhớ không được sử dụng, chương trình đó sẽ gây ra rò rỉ bộ nhớ, khiến cho mức sử dụng bộ nhớ tăng dần cho đến khi chương trình gặp sự cố hoặc có thể thấy rõ các hiệu ứng tiêu cực về hiệu suất.
- Bộ nhớ chia sẻ
-
Bộ nhớ chia sẻ là một loại cơ chế giao tiếp giữa các tiến trình; nó cho phép nhiều tiến trình đọc và thao tác trên một vùng bộ nhớ chung. Bộ nhớ chia sẻ rất phổ biến đối với phần cứng (ví dụ như ổ đĩa) và cũng có thể là một cách hiệu quả để chia sẻ dữ liệu giữa các tiến trình. Tuy nhiên, cơ chế này đòi hỏi việc đồng bộ hóa và quản lý nghiêm ngặt để ngăn ngừa hỏng dữ liệu. Lỗi điều kiện đua (race condition) sẽ xảy ra nếu một tiến trình thực hiện thay đổi ngoài dự kiến đối với dữ liệu trong khi một tiến trình khác đang sử dụng dữ liệu đó.
- Truyền tin
-
Truyền tin là một cơ chế giao tiếp giữa các tiến trình để cho phép chúng trao đổi dữ liệu và điều phối các hoạt động. Điều này thường được sử dụng trong lập trình đồng thời để đạt được giao tiếp giữa các tiến trình và có thể được thực hiện thông qua các cơ chế khác nhau như ổ nối, đường ống hoặc hàng đợi tin.
- Thu gom rác
-
Thu gom rác là một kỹ thuật quản lý bộ nhớ tự động được một số ngôn ngữ lập trình sử dụng để lấy lại bộ nhớ không còn được sử dụng trong khi một tiến trình đang chạy. Điều này có thể giúp ngăn ngừa rò rỉ bộ nhớ và giúp các nhà phát triển viết mã một cách chính xác và hiệu quả hơn, nhưng nó cũng có thể gây ra tiêu hao hiệu năng chênh lệch và khiến việc kiểm soát hành vi chính xác của chương trình trở nên khó khăn hơn.
- Kiểu dữ liệu
-
Kiểu dữ liệu sẽ xác định loại thông tin nào có thể được biểu diễn trong chương trình. Các kiểu dữ liệu có thể được xác định sẵn bằng ngôn ngữ hoặc do người dùng xác định và có thể bao gồm các số nguyên, số dấu phẩy động (nghĩa là xấp xỉ số thực), chuỗi, mảng và các loại khác.
- Đầu vào và đầu ra (I/O)
-
Đầu vào và đầu ra là các cơ chế đọc và ghi dữ liệu vào và ra khỏi một chương trình. Đầu vào có thể đến từ nhiều nguồn khác nhau như nhấp chuột, đầu vào bàn phím, tệp hoặc kết nối mạng, trong khi đầu ra có thể được gửi đến nhiều đích khác nhau như màn hình, tệp hoặc kết nối mạng. I/O cho phép các chương trình tương tác với thế giới bên ngoài và trao đổi thông tin với các hệ thống khác.
- Xử lý lỗi
-
Xử lý lỗi có nghĩa là phát hiện và phản hồi các lỗi xảy ra trong quá trình thực thi chương trình. Điều này bao gồm các lỗi như chia cho 0 và không tìm thấy tệp được yêu cầu. Xử lý lỗi cho phép các chương trình tiếp tục chạy ngay cả khi xảy ra lỗi và cải thiện độ tin cậy cũng như độ bền của chúng.
Các khái niệm được liệt kê ở trên chính là nền tảng để hiểu về cách hoạt động của ngôn ngữ lập trình cũng như phương pháp viết mã hiệu quả và dễ bảo trì.
Cú pháp của Ngôn ngữ Lập trình
Cú pháp của ngôn ngữ lập trình đề cập đến các quy tắc viết câu lệnh và biểu thức chương trình. Điều quan trọng là cú pháp phải được xác định một cách rõ ràng và nhất quán để lập trình viên có thể viết và hiểu mã của họ một cách hiệu quả. Sau đây là các khối cơ bản của hầu hết các ngôn ngữ lập trình:
- Quy trình và Hàm
-
Các quy trình và hàm được sử dụng để xác định các khối mã có thể tái sử dụng và có thể được gọi nhiều lần.
- Biến
-
Biến đại diện cho các phần bộ nhớ và dữ liệu lưu trữ có thể được thao tác và truyền giữa các quy trình và hàm.
- Toán tử
-
Toán tử là các từ khóa hoặc ký hiệu (chẳng hạn như
+
và-
) dùng để gán giá trị cho các biến và thực hiện các phép toán số học. - Cấu trúc điều khiển
-
Nói chung, mã chương trình sẽ được thực thi theo thứ tự được viết, nhưng các câu lệnh điều kiện sẽ thay đổi luồng thực thi. Việc mã nào được thực thi tiếp theo sẽ dựa trên các điều kiện khác nhau như nội dung của bộ nhớ, trạng thái của bàn phím, các gói đến từ mạng, v.v. Câu lệnh vòng lặp (loop statement) là một dạng câu lệnh điều kiện đặc biệt và rất hữu ích trong việc thực hiện các thao tác tương tự nhau trên một chuỗi tập dữ liệu. Một ngoại lệ, hay chính là một cấu trúc điều khiển khác, sẽ gọi mã đặc biệt khi xảy ra lỗi.
Mỗi ngôn ngữ lập trình đều có thể có các cú pháp và hành vi khác nhau, và việc lựa chọn ngôn ngữ có thể có tác động lớn đến khả năng đọc và bảo trì mã.
Thư viện
Một ngôn ngữ lập trình tốt sẽ giúp người dùng dễ dàng phát triển chương trình và dễ dàng sử dụng lại mã có sẵn. Nhiều ngôn ngữ lập trình đều có cơ chế tổ chức các quy trình và hàm thành các phần có thể được tái sử dụng trong các chương trình khác.
Thư viện là nơi tập hợp các quy trình và hàm nhằm hỗ trợ một tính năng hoặc mục tiêu cụ thể và được kết hợp thành một tệp duy nhất. Việc có sẵn nhiều thư viện dễ sử dụng là một yêu cầu rất quan trọng khác đối với một ngôn ngữ lập trình tốt. Ví dụ: Python được công nhận rộng rãi là một ngôn ngữ tốt để phát triển các chương trình liên quan đến AI vì nó có một số lượng lớn thư viện phù hợp với việc xử lý AI.
Với quy mô và độ phức tạp ngày càng tăng của các chương trình, thư viện giống những khối gạch có sẵn và đang ngày càng trở nên quan trọng hơn. Điều này đặc biệt đúng trong thế giới mã nguồn mở nơi mọi người đều có thể cảm thấy thoải mái với việc lấy mã do người khác tạo ra và sử dụng lại nó. Do đó, một hệ sinh thái thư viện đã được phát triển cho từng ngôn ngữ lập trình và các trình quản lý gói như composer
cho PHP, pip
cho Python và gems
cho Ruby sẽ giúp cài đặt các thư viện một cách dễ dàng.
Thư viện cũng rất quan trọng đối với ngôn ngữ biên dịch. Việc kết hợp nhiều tệp nhị phân và thư viện đã được biên dịch sẵn để có được một tệp thực thi duy nhất được gọi là liên kết (linking) và công cụ thực hiện thao tác này được gọi là trình liên kết (linker). Có hai loại liên kết: liên kết tĩnh (static linking) nơi chỉ có mã thư viện cần thiết được đưa vào tệp thực thi của ứng dụng cuối cùng và liên kết động (dynamic linking) nơi một thư viện được cài đặt trong hệ thống sẽ được chia sẻ bởi tất cả các ứng dụng sử dụng thư viện đó. Hiện tại, liên kết động là phương pháp được ưa chuộng hơn và đặc trưng bởi các tệp thực thi ứng dụng nhỏ hơn và sử dụng ít bộ nhớ hơn khi chạy.
Hãy lưu ý rằng vì cùng một thư viện có thể được sử dụng bởi nhiều chương trình nên sự khác biệt giữa các phiên bản của một thư viện có thể còn là một vấn đề lớn hơn so với các ứng dụng. Ngoài lề một chút, chúng ta sẽ phải ghi nhớ cách xem số phiên bản. Phân phiên bản ngữ nghĩa (Semantic versioning) được sử dụng phổ biến và biểu thị các phiên bản bằng ba số được phân tách bằng dấu chấm. Một phiên bản điển hình có thể là 2.39.16
cho biết phiên bản chính là 2 (một con số có thể chỉ thay đổi vài năm một lần), phiên bản phụ là 39 nằm trong phiên bản chính (có thể cập nhật vài tháng một lần để chứa các thay đổi quan trọng về tính năng) và bản sửa đổi nhanh là 16 (có thể thay đổi do sửa một lỗi duy nhất). Các phiên bản và sửa đổi càng về sau sẽ có số càng cao hơn.
Một ví dụ rất đơn giản
Chúng ta hãy cùng xem một ví dụ rất đơn giản về một chương trình máy tính sử dụng ngôn ngữ Python để có thể mường tượng sơ bộ về một số yếu tố đã được đề cập tới.
Được trình bày bằng ngôn ngữ tự nhiên, chương trình phải thực hiện những việc sau: "Yêu cầu người dùng nhập một số và kiểm tra xem số này là chẵn (EVEN) hay lẻ (ODD). Cuối cùng là xuất kết quả."
Và đây là đoạn mã chúng ta có thể lưu trong tệp simpleprogram.py
:
num = int(input("Enter a number: "))
if (num % 2) == 0:
print("The given number is EVEN.")
else:
print("The given number is ODD.")
Chỉ trong vài dòng mã này, chúng ta đã có thể tìm thấy nhiều đặc điểm và thành phần cú pháp đã được đề cập tới ở trên:
-
Trong dòng 1, chúng ta đã đặt biến
num
và gán cho nó một giá trị với toán tử=
. -
Giá trị được gán tương ứng với đầu vào của người dùng (thông qua
input()
function ). Ngoài ra, hàmint()
sẽ đảm bảo rằng đầu vào này sẽ được chuyển đổi thành kiểu dữ liệu số nguyên nếu có thể. Biểu thức được truyền cho hàm trong dấu ngoặc đơn được gọi là tham số (parameter) hoặc đối số (argument). -
Nếu người dùng nhập một chuỗi ký tự, Python sẽ in ra lỗi. Việc in ra lỗi này là một phần của quá trình xử lý lỗi. Nếu một số thập phân được nhập vào, hàm
int()
sẽ chuyển đổi nó thành số cơ sở (ví dụ:5,73
đến5
). -
Trong các dòng tiếp theo, cấu trúc điều khiển nêu ra một điều kiện với từ khóa
if
vàelse
sẽ kiểm soát những gì sẽ xảy ra trong mỗi trường hợp (số chẵn hoặc số lẻ). -
Đầu tiên, toán tử modulo
%
kiểm tra xem khi (if
) chia số đã nhập cho 2 có cho ra giá trị 0 (tức là không có số dư) hay không — trong trường hợp này, số đó là số chẵn. Ký tự==
được nhân đôi là toán tử so sánh “bằng” khác với toán tử gán=
ở dòng 1. -
Trong trường hợp khác (
else
), tức là khi chia cho 2 kết quả không bằng 0 thì số được nhập là số lẻ. -
Trong cả hai trường hợp, hàm
print()
đều sẽ trả về kết quả dưới dạng đầu ra bằng văn bản.
Và đây là kết quả khi chúng ta chạy chương trình trên dòng lệnh:
$ python simpleprogram.py Enter a number: 5 The given number is ODD.
Khi xem xét mức độ logic ngôn ngữ đã được sử dụng trong ví dụ đơn giản này, chúng ta sẽ biết được phần mềm phức tạp được phân phối trên hàng nghìn tệp có khả năng thực hiện những gì. Ví dụ: hệ điều hành (như Microsoft Windows, macOS hoặc Linux) sẽ khiến tất cả phần cứng của máy tính trở nên khả dụng, đồng thời đảm bảo rằng người dùng có thể cài đặt tất cả các ứng dụng khác mà họ mong muốn và sử dụng chúng cho công việc hoặc mục đích giải trí.
Mã máy, Hợp ngữ và Trình biên dịch mã
Như đã đề cập trước đây, phần cứng chỉ có thể thực thi trực tiếp một loạt mẫu bit được gọi là mã máy. CPU sẽ đọc mẫu bit từ bộ nhớ theo đơn vị của một từ (8 đến 64 bit) và thực hiện lệnh tương ứng. Các lệnh riêng lẻ đều khá là đơn giản, ví dụ: sao chép nội dung của vị trí bộ nhớ A sang vị trí bộ nhớ B, nhân nội dung của vị trí bộ nhớ C với nội dung của vị trí bộ nhớ D hoặc đọc dữ liệu đã đến thiết bị tại địa chỉ X. Trong thời đại CPU 8 bit, một số người có thể ghi nhớ tất cả các mẫu bit được sử dụng trong mã máy và viết chương trình một cách trực tiếp. Ngày nay, số lượng mẫu chỉ dẫn đã tăng lên theo cấp độ lớn và việc cố gắng ghi nhớ tất cả các mẫu này là không thực tế.
Mã máy là một chuỗi các mẫu bit, có thể là 0 và 1, hoàn toàn không hề trực quan đối với con người. Để việc lập trình có thể trở nên trực quan hơn, hợp ngữ (assembly language) đã được tạo ra, trong đó các chỉ dẫn sẽ được đặt tên và có thể được chỉ định bằng chuỗi. Trong hợp ngữ, các chỉ dẫn tương ứng 1-1 với mã máy sẽ được viết từng lệnh một. Các chỉ dẫn có thể sẽ giống như sau:
move [B], [A] copy the contents of memory A to memory B multi R1, [C], [D] multiply the contents of memory C by the contents of memory D input R1, [X] read the data that has arrived at the device at address X
Một chỉ dẫn trong hợp ngữ sẽ tương ứng với một lệnh trong mã máy; lệnh này sẽ tương ứng với lệnh chính xác mà phần cứng có thể hiểu và thực hiện. Ưu điểm của hợp ngữ so với ngôn ngữ máy gồm có:
- Cải thiện khả năng đọc và bảo trì
-
Hợp ngữ dễ đọc và viết hơn nhiều so với mã máy. Điều này sẽ giúp các lập trình viên dễ dàng hiểu, gỡ lỗi và duy trì mã của họ hơn.
- Tự động hóa việc tính toán địa chỉ
-
Việc lập trình mã máy cũng có thể sử dụng khái niệm biến và hàm, nhưng mọi thứ phải được thể hiện dưới dạng địa chỉ bộ nhớ. Hợp ngữ cũng sẽ gán tên cho các địa chỉ bộ nhớ để giúp việc diễn đạt logic của chương trình trở nên dễ dàng hơn.
Vì hợp ngữ có quyền truy cập vào tất cả chức năng của phần cứng nên nó thường được sử dụng trong các tình huống sau:
- Phần phụ thuộc kiến trúc của hệ điều hành
-
Việc sử dụng các chỉ dẫn chuyên dụng dành riêng cho một kiến trúc CPU (để truy cập vào các tính năng khởi tạo và bảo mật) chỉ có thể được thực hiện bằng hợp ngữ.
- Phát triển các thành phần hệ thống cấp thấp
-
Hợp ngữ được sử dụng để phát triển các thành phần hệ thống cần tương tác trực tiếp với phần cứng của máy tính như trình điều khiển thiết bị, chương trình cơ sở (firmware) và hệ thống đầu vào/đầu ra cơ bản (BIOS). Đặc biệt, các thiết bị tốc độ cao yêu cầu đẩy hiệu suất phần cứng đến giới hạn thường cần tới các trình điều khiển và chương trình cơ sở được lập trình bằng hợp ngữ.
- Lập trình bộ vi điều khiển
-
Hợp ngữ cũng được sử dụng để lập trình bộ vi điều khiển - tức những máy tính nhỏ có công suất thấp được sử dụng trong nhiều hệ thống nhúng từ đồ chơi đến điều khiển công nghiệp. Một số bộ vi điều khiển chỉ có dung lượng bộ nhớ là vài trăm byte và thường được lập trình bằng hợp ngữ.
Mã hợp ngữ sẽ được chuyển đổi thành mã máy trước khi được thực thi bởi một ứng dụng có tên trình biên dịch mã (assembler). Trình biên dịch mã là công cụ lập trình lâu đời nhất và đã mang lại một số lợi thế không thể tưởng tượng được trong lập trình mã máy. Vì dễ gây nhầm lẫn nên đôi khi người ta conf gọi hợp ngữ là trình biên dịch mã.
Mã máy và hợp ngữ khác nhau ở các bộ xử lý phần cứng. Những ngôn ngữ này được gọi là “ngôn ngữ cấp thấp” vì chúng hoạt động trực tiếp trên phần cứng. Tuy nhiên, các khái niệm tính toán và đầu vào/đầu ra đều giống nhau trên tất cả các bộ xử lý. Nếu các khái niệm chung có thể được diễn đạt theo cách dễ hiểu hơn đối với con người thì hiệu quả lập trình có thể được cải thiện đáng kể. Đây là nơi "ngôn ngữ bậc cao" xuất hiện.
Ngôn ngữ biên dịch
Ngôn ngữ biên dịch là ngôn ngữ lập trình được dịch sang mã máy hoặc sang định dạng trung gian gọi là bytecode. Bytecode sẽ được thực thi trên máy tính đích bởi máy ảo thời gian chạy. Máy ảo sẽ dịch mã byte thành mã máy thích hợp cho mỗi máy tính. Bytecode cho phép các chương trình không phụ thuộc vào nền tảng và có thể chạy trên bất kỳ hệ thống nào có máy ảo tương thích.
Việc dịch mã nguồn được viết bằng ngôn ngữ lập trình bậc cao sang mã máy hoặc mã byte sẽ được thực hiện bởi trình biên dịch. Ví dụ về các ngôn ngữ được biên dịch tạo ra mã máy trực tiếp chính là C và C++. Các ngôn ngữ tạo ra bytecode gồm có Java và C#. Việc lựa chọn giữa mã máy và bytecode phụ thuộc vào các yêu cầu của dự án như hiệu suất, tính bất khả tri về nền tảng (không bị ràng buộc trên một nền tàng cụ thể) và tính dễ phát triển.
Ngôn ngữ thông dịch
Ngôn ngữ thông dịch là các ngôn ngữ lập trình được thực thi bởi một trình thông dịch thay vì được biên dịch thành mã máy. Trình thông dịch sẽ đọc mã nguồn và thực thi các câu lệnh có trong đó. Trình thông dịch có thể xử lý trực tiếp mã nguồn mà không cần chuyển đổi nó sang một định dạng tệp khác. Do đó, không giống như trình biên dịch là dịch toàn bộ chương trình thành mã máy trước khi thực thi, trình thông dịch sẽ đọc từng dòng mã và thực thi nó ngay lập tức. Việc này cho phép lập trình viên có thể xem được kết quả của từng dòng khi chúng được thực thi.
Ngôn ngữ thông dịch thường được sử dụng cho tệp lệnh - tức là các chương trình ngắn tự động hóa các tác vụ - dành cho giao diện dòng lệnh và để kiểm soát công việc và các tác vụ hàng loạt. Các tệp lệnh được viết bằng ngôn ngữ thông dịch có thể dễ dàng được sửa đổi và thực thi mà không cần qua biên dịch lại. Điều này khiến chúng phù hợp với các tác vụ đòi hỏi tạo mẫu nhanh hoặc lặp lại linh hoạt và nhanh chóng. Sự tiện lợi này cũng đi kèm với một số nhược điểm tiềm ẩn. Ví dụ: một chương trình được thông dịch sẽ chạy chậm hơn một chương trình được biên dịch tương đương.
Một số ví dụ về các ngôn ngữ thông dịch chính là Python, Ruby và JavaScript. Python được sử dụng rộng rãi trong tính toán khoa học, phân tích dữ liệu và máy học tự động (machine learning), trong khi Ruby thường được sử dụng trong phát triển trang web và tạo các tệp lệnh tự động hóa. JavaScript là một ngôn ngữ tệp lệnh phía máy khách được nhúng trong trình duyệt web để tạo các trang web động và tương tác.
Ngôn ngữ hướng Dữ liệu
Ngôn ngữ hướng dữ liệu là ngôn ngữ lập trình được tối ưu hóa để xử lý và thao tác với các số lượng dữ liệu lớn. Chúng được thiết kế để có thể xử lý hiệu quả các tập hợp dữ liệu lớn có cấu trúc hoặc không cấu trúc cũng như cung cấp một bộ công cụ để làm việc với cơ sở dữ liệu, cấu trúc dữ liệu và thuật toán để xử lý và phân tích dữ liệu.
Ngôn ngữ hướng dữ liệu được ứng dụng trong nhiều khía cạnh như khoa học dữ liệu, phân tích dữ liệu lớn, máy học tự động và lập trình cơ sở dữ liệu. Chúng rất phù hợp cho các nhiệm vụ liên quan đến xử lý và phân tích các số lượng dữ liệu lớn như làm sạch và chuyển đổi dữ liệu, trực quan hóa dữ liệu và lập mô hình thống kê.
Một số ví dụ về các ngôn ngữ hướng dữ liệu chính là SQL (viết tắt của Structured Query Language - Ngôn ngữ truy vấn có cấu trúc), R và MATLAB. SQL là ngôn ngữ tiêu chuẩn được sử dụng để quản lý cơ sở dữ liệu quan hệ và được sử dụng rộng rãi trong kinh doanh và công nghiệp. R là một ngôn ngữ lập trình và môi trường dành cho cho tính toán và đồ hoạ thống kê, đồng thời cũng được sử dụng rộng rãi trong khoa học dữ liệu và máy học tự động. MATLAB là một môi trường tính toán số và ngôn ngữ lập trình được ứng dụng trong nhiều tác vụ như xử lý tín hiệu, xử lý hình ảnh và tài chính tính toán.
Mô hình Lập trình
Bên cạnh các đặc điểm cụ thể của ngôn ngữ lập trình, mô hình lập trình (programming paradigm) sẽ xác định cách tiếp cận giải pháp cụ thể. Chúng ta có thể coi mô hình như một chiến lược cơ bản để tiếp cận một nhiệm vụ tùy thuộc vào các yêu cầu và điều kiện cụ thể.
Một ví dụ để dễ hình dung chính là việc xây dựng một ngôi nhà: việc thợ xây xây tường bằng gạch hay bằng các khối bê tông đúc sẵn được lắp ráp tại chỗ là một quyết định cơ bản tùy thuộc vào yêu cầu và hoàn cảnh. Chúng ta muốn ngôi nhà có những đặc điểm gì? Nó nằm ở đâu? Nó có được kết nối với những ngôi nhà khác hay không?
Theo một cách tương tự, các mô hình sẽ đặt ra phương hướng lập trình: ví dụ như một dự án phần mềm liệu có thể được chia thành các phần nhỏ riêng biệt hay không và bằng cách nào. Mỗi ngôn ngữ lập trình sẽ phù hợp nhất với một số mô hình cụ thể. Vì vậy, việc lựa chọn mô hình có liên quan chặt chẽ đến việc lựa chọn ngôn ngữ lập trình.
Các mô hình sau đây rất phổ biến trong lập trình:
- Lập trình hướng đối tượng (Object-oriented programming - OOP)
-
OOP được dựa trên khái niệm về đối tượng - tức là các thành phần của hạng (class) bao hàm cả dữ liệu và hành vi. Ví dụ: một ngôn ngữ có thể cung cấp một hình chữ nhật như một hạng để giúp lập trình viên hiển thị hộp khung trên màn hình.
OOP tập trung vào thao tác dữ liệu ở cấp độ đối tượng. OOP sẽ giúp việc viết mã trở nên dễ bảo trì, tái sử dụng và mở rộng hơn và được sử dụng rộng rãi trong các phần mềm máy tính để bàn, trò chơi video và ứng dụng web. Java, C# và Python chính là các ví dụ về ngôn ngữ lập trình hướng đối tượng.
- Lập trình thủ tục (Procedural programming)
-
Lập trình thủ tục sẽ thực hiện tác vụ thông qua các quy trình hoặc các khối mã có thể được thực thi theo một thứ tự cụ thể. Điều này sẽ giúp chúng ta dễ dàng viết mã có cấu trúc và dễ theo dõi, nhưng nó cũng có thể dẫn đến việc mã trở nên kém linh hoạt và khó bảo trì hơn khi quy mô và độ phức tạp của dự án tăng lên. C, Pascal và Fortran là các ví dụ về các ngôn ngữ lập trình thủ tục.
Ngày nay, các cách tiếp cận khác nhằm phát triển phần mềm đang được sử dụng; một số ngôn ngữ nhất định sẽ phù hợp hơn với các cách tiếp cận này. Ngoài ra, giao diện kéo và thả sẽ cho phép những người không phải lập trình viên có thể viết chương trình và gần đây nhiều dịch vụ trực tuyến đã bắt đầu có khả năng tạo ra mã với trí tuệ nhân tạo thông qua các chỉ dẫn bằng ngôn ngữ đơn giản.
Tóm lại, mỗi một mô hình lập trình đều có điểm mạnh và điểm yếu riêng và việc lựa chọn mô hình thường phụ thuộc vào nhu cầu của dự án, kinh nghiệm và sở thích của nhà phát triển cũng như các hạn chế của nền tảng và môi trường phát triển. Việc hiểu về các loại mô hình khác nhau sẽ giúp chúng ta chọn ra được các mô hình phù hợp với nhu cầu của mình và cũng có thể giúp chúng ta viết mã một cách hiệu quả hơn.
Bài tập Hướng dẫn
-
Mục đích của hàm là gì?
-
Ưu điểm của bytecode so với tệp mã máy là gì?
-
Ưu điểm của tệp mã máy so với bytecode là gì?
Bài tập Mở rộng
-
Một số nhược điểm của việc chia chương trình thành một số lượng lớn các tiến trình hoặc tác vụ là gì?
-
Bạn đã tìm thấy một số gói mã nguồn mở được cung cấp ở các phiên bản khác nhau. Các gói này cung cấp các tính năng mà bạn cần cho chương trình của mình. Các tiêu chí để lựa chọn một gói là gì?
-
Ngoài OOP và các mô hình phát triển thủ tục, có những phương pháp phát triển phần mềm nào khác và ngôn ngữ lập trình nào hỗ trợ tốt nhất cho từng phương pháp?
Tóm tắt
Trong bài học này, chúng ta đã tìm hiểu phần mềm là gì và nó được phát triển như thế nào với sự trợ giúp của các ngôn ngữ lập trình. Các ngôn ngữ lập trình khác nhau không chỉ về cú pháp mà còn về cách quản lý tài nguyên phần cứng hoặc cách xử lý cấu trúc dữ liệu.
Các ngôn ngữ lập trình cũng khác nhau về cách mã nguồn (con người có thể đọc được) sẽ được trình thông dịch hoặc trình biên dịch chuyển đổi thành mã máy cuối cùng để máy tính xử lý như thế nào.
Mô hình lập trình xác định chiến lược của các dự án phần mềm và do đó cũng sẽ quyết định việc lựa chọn ngôn ngữ lập trình phù hợp tùy thuộc vào yêu cầu và quy mô của dự án tương ứng.
Đáp án Bài tập Hướng dẫn
-
Mục đích của hàm là gì?
Hàm gói gọn một số hoạt động phổ biến nhất định, chẳng hạn như việc xuất ra một chuỗi. Bằng việc tạo một hàm, chúng ta có thể cho phép chương trình của mình và các chương trình khác thực hiện hàm đó một cách thuận tiện và lặp đi lặp lại mà không cần phải viết mã riêng cho nó.
-
Ưu điểm của bytecode so với tệp mã máy là gì?
Tệp bytecode có thể chạy trên nhiều máy tính khác nhau nơi máy ảo sẽ biến mã thành mã máy. Ví dụ: JavaScript chạy trong nhiều trình duyệt trên nhiều loại máy tính.
-
Ưu điểm của tệp mã máy so với bytecode là gì?
Mã máy sẽ chạy một cách nhanh nhất có thể. Bytecode sẽ chạy chậm hơn vì máy ảo phải biến nó thành mã máy khi chạy bytecode.
Đáp án Bài tập Mở rộng
-
Một số nhược điểm của việc chia chương trình thành một số lượng lớn các tiến trình hoặc tác vụ là gì?
Khi một chương trình được chia thành các tiến trình, chúng sẽ phải giao tiếp với nhau. Nếu chúng làm việc chung trên nhiều dữ liệu thì các tiến trình có thể tiêu tốn chênh lệch trong việc trao đổi dữ liệu và bảo vệ dữ liệu khỏi nhiều thay đổi đồng thời (điều kiện đua). Các tiến trình cũng sẽ phải chịu mức chênh lệch khi chúng khởi động và kết thúc. Càng có nhiều tiến trình thì chương trình và các tương tác của nó càng trở nên phức tạp hơn, do đó khó có thể tìm ra lỗi hơn.
Mô hình hàm có xu hướng giúp việc chia chương trình thành nhiều tiến trình trở nên dễ dàng hơn vì tính bất biến. Dữ liệu bất biến sẽ không bị ảnh hưởng bởi các điều kiện đua.
-
Bạn đã tìm thấy một số gói mã nguồn mở được cung cấp ở các phiên bản khác nhau. Các gói này cung cấp các tính năng mà bạn cần cho chương trình của mình. Các tiêu chí để lựa chọn một gói là gì?
Kiểm tra các báo cáo lỗi và tư vấn bảo mật cho các gói vì một số gói sẽ có rất nhiều lỗi và thậm chí là không an toàn. Đôi khi phiên bản mới nhất không phải là phiên bản tốt nhất vì có thể nó đã có một lỗ hổng bảo mật mới nào đó.
Hãy xem qua diễn đàn nơi các nhà phát triển nói về gói để biết chắc rằng nó vẫn đang được duy trì. Chương trình của bạn có thể sẽ cần được sử dụng trong một thời gian dài và bạn cũng sẽ muốn gói này luôn vững chắc và sẵn sàng theo thời gian.
Hãy thử các gói khác nhau để kiểm tra hiệu suất cũng như tính chính xác của chúng.
Hầu hết các gói đều phụ thuộc vào các hàm có trong các gói khác (phần phụ thuộc); do đó, điểm yếu ở một trong các gói phụ thuộc có thể sẽ ảnh hưởng đến chương trình của bạn.
-
Ngoài OOP và các mô hình phát triển thủ tục, có những phương pháp phát triển phần mềm nào khác và ngôn ngữ lập trình nào hỗ trợ tốt nhất cho từng phương pháp?
Ngoài OOP và các mô hình phát triển thủ tục, chúng ta còn có các loại khác như sau:
Lập trình hàm nhấn mạnh việc sử dụng các hàm và khái niệm toán học (như lambda và toán tử bao đóng) để viết mã dựa trên việc đánh giá các biểu thức thay vì thực thi các câu lệnh. Lập trình hàm xử lý các hàm như những công dân hạng nhất — để chương trình có thể thao tác chúng — và nhấn mạnh tính bất biến, hoặc làm việc với các biến không thể được thay đổi sau thiết lập ban đầu. Điều này giúp việc suy luận và kiểm tra mã cũng như viết các ứng dụng đồng thời và song song trở nên dễ dàng hơn. Erlang, Haskell, Lisp và Scheme là các ví dụ về ngôn ngữ lập trình hàm.
Các ngôn ngữ mệnh lệnh tập trung vào các câu lệnh cần thiết để kiểm soát luồng chuyển đổi của chương trình từ và sang các trạng thái khác nhau.
Ngôn ngữ khai báo mô tả những hành động cần thực hiện và logic đằng sau các chỉ dẫn. Thứ tự thực hiện các chỉ dẫn sẽ không được chỉ định. Các chỉ dẫn, lệnh gọi hàm và các câu lệnh khác này có thể được trình biên dịch sắp xếp lại và tối ưu hóa, miễn là chúng vẫn duy trì logic cơ bản.
Lập trình tự nhiên là một mô hình lập trình sử dụng ngôn ngữ tự nhiên hoặc các cách biểu diễn thân thiện với con người khác để mô tả hành vi cần có của chương trình. Ý tưởng ở đây là làm cho việc lập trình có thể tiếp cận được với những người không được đào tạo chính quy về khoa học máy tính. Scratch và Alic là các ví dụ về ngôn ngữ lập trình tự nhiên.