Skip to content

Quy trình phát triển phần mềm

Trang này giới thiệu các quy trình phát triển phần mềm thường dùng trong dự án embedded và sản phẩm có phần mềm đi kèm phần cứng. Nội dung đi từ vòng đời phát triển phần mềm, qua các mô hình tổ chức công việc, tới các hoạt động xuyên suốt (quản lý yêu cầu, code review, CI/CD, kiểm thử, release), và kết thúc bằng cách phần mềm khớp với các giai đoạn phát triển phần cứng.

Góc nhìn phần cứng của cùng chủ đề, gồm các giai đoạn EVT, DVT, PVT và việc đưa sản phẩm vào sản xuất, được trình bày ở trang Quy trình phát triển sản phẩm phần cứng.

Vòng đời phát triển phần mềm (SDLC)

Vòng đời phát triển phần mềm (Software Development Life Cycle, SDLC) là chuỗi các hoạt động từ khi xác định yêu cầu tới khi phần mềm được phát hành và bảo trì. Mọi mô hình phát triển đều có các hoạt động này; các mô hình ở mục sau khác nhau ở cách sắp xếp chúng: thực hiện tuần tự một lần, hay lặp lại nhiều vòng ngắn.

Hoạt động Đầu vào chính Đầu ra chính
Phân tích yêu cầu Nhu cầu sản phẩm, đặc tả phần cứng, ràng buộc của thị trường Đặc tả yêu cầu phần mềm
Thiết kế Đặc tả yêu cầu Kiến trúc, thiết kế module, đặc tả giao diện giữa các thành phần
Hiện thực Tài liệu thiết kế Mã nguồn, unit test
Kiểm thử Mã nguồn đã build, đặc tả yêu cầu Báo cáo kiểm thử, danh sách lỗi
Phát hành Bản build đã qua kiểm thử Bản release, release note
Bảo trì Báo cáo lỗi từ thiết bị đang dùng, yêu cầu mới Bản vá, bản cập nhật

Phần mềm nhúng có hai đặc điểm ảnh hưởng tới vòng đời này. Thứ nhất, phần cứng đích thường chưa có khi phần mềm bắt đầu được viết, nên giai đoạn đầu phải phát triển trên board phát triển (development kit) hoặc trên trình giả lập. Thứ hai, sau khi sản phẩm được bán ra, bảo trì đồng nghĩa với cập nhật firmware trên thiết bị ở hiện trường, việc này cần cơ chế OTA Update được thiết kế từ trước.

Các mô hình phát triển

Mục này trình bày các mô hình Waterfall, V-model, Agile cùng hai framework phổ biến của Agile là Scrum và Kanban, sau đó so sánh chúng theo cách lập kế hoạch, thời điểm kiểm thử và mức độ chấp nhận thay đổi yêu cầu.

Waterfall

Mô hình Waterfall (thác nước) thực hiện các hoạt động của SDLC theo thứ tự, hoạt động sau chỉ bắt đầu khi hoạt động trước đã hoàn thành và được duyệt. Mô hình này thường được gắn với bài báo của Winston Royce năm 1970. Bài báo mô tả chuỗi tuần tự đó, nhưng cũng chỉ ra rủi ro của nó: lỗi thiết kế chỉ lộ ra ở giai đoạn kiểm thử cuối cùng, khi chi phí sửa đã cao. Vì vậy Royce đề xuất thêm các vòng phản hồi giữa các giai đoạn.

Waterfall cho kế hoạch và tài liệu rõ ràng ở từng giai đoạn. Đổi lại, thay đổi yêu cầu sau khi đã qua giai đoạn phân tích có chi phí lớn, vì phải quay lại các giai đoạn trước.

V-model

V-model mở rộng Waterfall bằng cách ghép mỗi bước đặc tả và thiết kế với một mức kiểm thử tương ứng. Nhánh trái đi từ yêu cầu xuống thiết kế chi tiết, nhánh phải đi từ unit test lên acceptance test. Mỗi mức kiểm thử kiểm chứng sản phẩm của bước thiết kế cùng mức ở nhánh trái.

V-model

Điểm khác với Waterfall là kế hoạch và tiêu chí kiểm thử được viết cùng lúc với bước thiết kế tương ứng, không đợi tới khi có code: tiêu chí acceptance test được xác định khi phân tích yêu cầu, kế hoạch integration test được viết khi thiết kế kiến trúc. Cách ghép cặp này phù hợp với các dự án cần chứng minh từng yêu cầu đã được kiểm chứng.

Agile

Agile là tập hợp các giá trị và nguyên tắc phát triển phần mềm được công bố trong Agile Manifesto năm 2001. Manifesto nêu bốn giá trị, mỗi giá trị ưu tiên vế trái hơn vế phải, nhưng không bỏ vế phải:

Ưu tiên Hơn
Con người và sự tương tác Quy trình và công cụ
Phần mềm chạy được Tài liệu đầy đủ
Hợp tác với khách hàng Đàm phán hợp đồng
Phản hồi với thay đổi Làm theo kế hoạch

Từ các giá trị này, Agile phát triển phần mềm theo nhiều vòng lặp ngắn. Mỗi vòng tạo ra một phần phần mềm chạy được, nhận phản hồi, rồi điều chỉnh kế hoạch cho vòng sau. Agile không quy định cách làm cụ thể; Scrum và Kanban là hai framework hiện thực hóa các nguyên tắc này.

Scrum

Scrum tổ chức công việc thành các Sprint có độ dài cố định, tối đa một tháng. Theo Scrum Guide bản 2020, một Scrum Team có ba vai trò (accountability):

  • Product Owner: chịu trách nhiệm tối đa hóa giá trị của sản phẩm, quản lý và sắp xếp thứ tự Product Backlog.
  • Developers: những người tạo ra phần sản phẩm hoàn chỉnh (Increment) trong mỗi Sprint.
  • Scrum Master: chịu trách nhiệm giúp nhóm áp dụng Scrum hiệu quả và gỡ các trở ngại.

Mỗi Sprint gồm bốn sự kiện nằm bên trong nó: Sprint Planning (chọn việc và đặt Sprint Goal), Daily Scrum (họp hằng ngày tối đa 15 phút để điều chỉnh kế hoạch), Sprint Review (trình bày kết quả và nhận phản hồi) và Sprint Retrospective (rút kinh nghiệm về cách làm việc). Scrum có ba artifact, mỗi artifact gắn với một cam kết: Product Backlog với Product Goal, Sprint Backlog với Sprint Goal, và Increment với Definition of Done (tiêu chí để một hạng mục được coi là hoàn thành).

Kanban

Kanban quản lý công việc như một dòng chảy liên tục thay vì chia thành các vòng có độ dài cố định. Công việc được thể hiện trên bảng Kanban, mỗi cột là một trạng thái (ví dụ: chờ làm, đang làm, đang review, xong). Số hạng mục ở mỗi trạng thái bị giới hạn (WIP limit, Work In Progress limit): khi một cột đầy, nhóm phải hoàn thành việc đang làm trước khi kéo việc mới vào.

Kanban Guide mô tả ba hoạt động chính: định nghĩa và trực quan hóa quy trình làm việc, chủ động quản lý các hạng mục đang chạy trong quy trình, và liên tục cải tiến quy trình. Hiệu quả của dòng chảy được đo bằng các chỉ số như số hạng mục đang làm (WIP), số hạng mục hoàn thành trong một khoảng thời gian (throughput), thời gian từ khi bắt đầu tới khi xong một hạng mục (cycle time), và tuổi của hạng mục đang làm (work item age).

Kanban phù hợp với công việc có luồng yêu cầu đến không đều, như bảo trì, xử lý lỗi từ hiện trường hoặc hỗ trợ sản xuất.

Cần đối chiếu

Chi tiết về Scrum (vai trò, sự kiện, artifact) và Kanban (ba hoạt động, các chỉ số) ở trên được viết theo bản 2020 của Scrum Guide và Kanban Guide nhưng chưa đối chiếu lại với bản hiện hành trên trang chính thức.

So sánh các mô hình

Tiêu chí Waterfall V-model Scrum Kanban
Lập kế hoạch Một lần cho toàn dự án Một lần, kèm kế hoạch kiểm thử cho từng mức Theo từng Sprint, dựa trên Product Backlog Liên tục, kéo việc mới khi còn chỗ
Thời điểm kiểm thử Sau khi hiện thực xong Kế hoạch có từ lúc thiết kế, thực hiện sau hiện thực Trong mỗi Sprint Là một trạng thái trong dòng chảy
Thay đổi yêu cầu Chi phí cao Chi phí cao Đưa vào Sprint sau Đưa vào bất kỳ lúc nào
Tài liệu Đầy đủ ở mỗi giai đoạn Đầy đủ, có truy vết yêu cầu tới test Ở mức đủ dùng Ở mức đủ dùng
Phù hợp khi Yêu cầu ổn định, phạm vi rõ Cần chứng minh mọi yêu cầu đã được kiểm chứng Yêu cầu thay đổi, cần phản hồi sớm Luồng việc đến không đều

Các mô hình này có thể kết hợp. Với sản phẩm có phần cứng, kế hoạch tổng thể bám theo các mốc của phần cứng (xem mục cuối trang), trong khi phần mềm trong mỗi giai đoạn có thể được phát triển theo Sprint, và phần bảo trì sau khi bán ra được quản lý theo Kanban.

Quản lý yêu cầu

Yêu cầu phần mềm chia thành hai nhóm. Yêu cầu chức năng (functional requirement) mô tả phần mềm phải làm gì, ví dụ "thiết bị gửi dữ liệu cảm biến lên server mỗi 60 giây". Yêu cầu phi chức năng (non-functional requirement) mô tả phần mềm phải đạt chất lượng nào, ví dụ thời gian boot, mức tiêu thụ điện, dung lượng bộ nhớ, độ tin cậy hoặc bảo mật. Với thiết bị nhúng, yêu cầu phi chức năng thường ràng buộc cả lựa chọn phần cứng.

Một yêu cầu tốt có các đặc điểm sau:

  • Có mã định danh duy nhất để tham chiếu từ thiết kế, code và test.
  • Rõ nghĩa, chỉ có một cách hiểu.
  • Kiểm chứng được: có thể viết test để xác định yêu cầu đạt hay không. "Boot nhanh" không kiểm chứng được; "từ lúc cấp nguồn tới khi dịch vụ chính sẵn sàng không quá 5 giây" thì kiểm chứng được.
  • Có độ ưu tiên, để quyết định khi phải cắt giảm phạm vi.

Truy vết yêu cầu (requirement traceability) là liên kết mỗi yêu cầu với phần thiết kế, code và test case tương ứng. Truy vết theo chiều xuôi cho biết yêu cầu nào đã được hiện thực và kiểm thử; truy vết theo chiều ngược cho biết một đoạn code hay test case tồn tại vì yêu cầu nào, và yêu cầu nào bị ảnh hưởng khi sửa nó. Truy vết đặc biệt quan trọng ở các dự án phải tuân theo tiêu chuẩn an toàn chức năng, vì cần chứng minh mọi yêu cầu đã được hiện thực và kiểm thử. Ở dự án nhỏ, truy vết có thể làm đơn giản bằng cách ghi mã yêu cầu vào issue, commit message và tên test case.

Code review

Code review là việc một hoặc nhiều người khác đọc và duyệt thay đổi trước khi nó được đưa vào nhánh chính. Mục đích chính là phát hiện lỗi sớm, giữ code nhất quán với coding convention và thiết kế chung, và chia sẻ hiểu biết về code giữa các thành viên.

Có hai hình thức review phổ biến:

Hình thức Cách làm Ví dụ
Pull request (merge request) Thay đổi nằm trên một nhánh; reviewer bình luận trên giao diện web; CI chạy tự động trên nhánh đó GitHub, GitLab, Gerrit
Patch qua mailing list Thay đổi được gửi dưới dạng patch qua email; reviewer trả lời trực tiếp trên email Linux kernel, U-Boot

Quy trình review qua pull request thường gồm các bước:

  1. Tác giả tạo thay đổi nhỏ, tập trung vào một mục đích, kèm mô tả vì sao cần thay đổi.
  2. CI tự động build và chạy test; thay đổi không qua CI thì chưa cần review.
  3. Reviewer đọc thay đổi và bình luận.
  4. Tác giả sửa theo bình luận, hoặc giải thích nếu không đồng ý.
  5. Reviewer chấp thuận (approve), sau đó thay đổi được merge vào nhánh chính.

Hướng dẫn code review của Google khuyến nghị giữ thay đổi nhỏ, vì thay đổi nhỏ được review nhanh và kỹ hơn, và dễ revert khi có lỗi. Khi review, các điểm cần xem gồm: tính đúng của logic, xử lý lỗi và giá trị trả về, truy cập đồng thời giữa các thread hoặc với interrupt, quản lý tài nguyên (bộ nhớ, file descriptor, lock), tuân thủ convention, và thay đổi có kèm test hay không.

Linux kernel là ví dụ về review qua mailing list ở quy mô lớn: patch được gửi tới maintainer của subsystem, được review công khai rồi mới đi vào cây của maintainer. Quy trình này được mô tả trong tài liệu "Submitting patches" của kernel và trong trang Kernel Release Process.

Tích hợp và triển khai liên tục (CI/CD)

Continuous Integration (CI, tích hợp liên tục) là cách làm trong đó các thành viên đưa thay đổi vào nhánh chính thường xuyên, và mỗi lần tích hợp được kiểm tra bằng build và test tự động. Mục tiêu là phát hiện xung đột và lỗi ngay khi chúng xuất hiện, thay vì dồn tới một đợt tích hợp lớn trước ngày release.

Continuous Delivery (CD, chuyển giao liên tục) mở rộng CI: phần mềm luôn ở trạng thái có thể release, mọi thay đổi qua pipeline đều tạo ra một artifact sẵn sàng phát hành, nhưng quyết định phát hành vẫn do con người. Continuous Deployment (triển khai liên tục) đi thêm một bước: mọi thay đổi qua pipeline được tự động triển khai tới người dùng mà không cần phê duyệt thủ công.

Pipeline CI/CD cho phần mềm nhúng

Một pipeline CI/CD cho phần mềm nhúng thường gồm các bước:

  1. Build: cross compile cho kiến trúc đích, hoặc build cả image bằng Yocto hay Buildroot. Toolchain thường được đóng gói trong container để mọi máy build cho ra cùng kết quả (xem Docker cho môi trường build).
  2. Phân tích tĩnh và unit test: chạy công cụ phân tích tĩnh (static analysis) và unit test trên máy build, không cần phần cứng.
  3. Đóng gói artifact: lưu image, gói cập nhật và thông tin build (commit, phiên bản toolchain) để có thể truy lại về sau.
  4. Test trên board: nạp image lên board thật trong một hệ thống kiểm thử hardware-in-the-loop (HIL) và chạy test tích hợp, test hệ thống. Bước này có thể thay một phần bằng giả lập (xem QEMU cross-arch).
  5. Release hoặc triển khai: phát hành artifact, hoặc đẩy bản cập nhật tới thiết bị qua OTA.

Với thiết bị nhúng, triển khai nghĩa là cập nhật firmware trên thiết bị đang hoạt động ở hiện trường. Một bản cập nhật lỗi có thể làm thiết bị không khởi động được, nên bước này thường có thêm phê duyệt thủ công và cập nhật theo từng đợt (staged rollout) thay vì triển khai đồng loạt.

Chiến lược kiểm thử

Kiểm thử được chia thành các mức, mỗi mức nhắm tới một đối tượng khác nhau. Các mức dưới đây theo cách phân loại của ISTQB và tương ứng với nhánh phải của V-model.

Mức kiểm thử Đối tượng Môi trường thường dùng Ví dụ trong embedded
Unit test (component test) Một hàm hoặc module, tách biệt khỏi phần còn lại Máy build, phần cứng được thay bằng mock Kiểm tra hàm parse bản tin NMEA với các chuỗi đầu vào mẫu
Integration test Giao tiếp giữa các module hoặc giữa phần mềm và phần cứng Board thật hoặc giả lập Driver đọc đúng dữ liệu từ cảm biến qua I2C
System test Toàn bộ sản phẩm so với đặc tả yêu cầu Board thật, cấu hình như sản phẩm Đo thời gian boot, mức tiêu thụ điện, chạy liên tục nhiều ngày
Acceptance test Sản phẩm so với nhu cầu của khách hàng Môi trường sử dụng thực tế Khách hàng chạy thử thiết bị trong hệ thống của họ

Unit test chạy nhanh và không cần phần cứng, nên có thể chạy ở mọi lần commit; test trên board chạy chậm hơn và cần thiết bị, nên số lượng ít hơn và có thể chạy theo lịch. Để unit test được code embedded trên máy build, phần truy cập phần cứng cần được tách sau một lớp trừu tượng (ví dụ một tập hàm đọc/ghi thanh ghi) để thay bằng mock khi test.

Kiểm thử hồi quy (regression test) là chạy lại các test đã có sau mỗi thay đổi, để chắc thay đổi mới không làm hỏng tính năng cũ. Mỗi lỗi đã sửa nên có một test tái hiện lỗi đó, để lỗi không quay lại.

Quản lý release và phiên bản

Mỗi bản release cần một số phiên bản để phân biệt và tham chiếu. Semantic Versioning (SemVer) là quy ước đánh số phiên bản dạng MAJOR.MINOR.PATCH:

Thành phần Tăng khi
MAJOR Có thay đổi không tương thích ngược với giao diện công khai (API)
MINOR Thêm tính năng mới nhưng vẫn tương thích ngược
PATCH Chỉ sửa lỗi, tương thích ngược

Với firmware, "giao diện công khai" có thể là API cho ứng dụng, giao thức với server, hoặc định dạng dữ liệu lưu trên flash. Thay đổi định dạng dữ liệu lưu trữ mà bản cũ không đọc được là thay đổi không tương thích, dù không đổi API nào.

Trong Git, có hai cách tổ chức nhánh thường dùng:

  • Trunk-based development: mọi người tích hợp thường xuyên vào một nhánh chính; mỗi bản release là một tag trên nhánh chính. Cách này phù hợp với CI vì nhánh chính luôn được kiểm tra.
  • Release branch: khi chuẩn bị release, tách một nhánh riêng từ nhánh chính. Trên nhánh release chỉ nhận sửa lỗi, trong khi nhánh chính tiếp tục nhận tính năng mới. Sửa lỗi được làm trên nhánh chính rồi backport sang nhánh release, hoặc ngược lại.

Mỗi bản release nên tái tạo lại được (reproducible): từ tag trong Git, cấu hình build và phiên bản toolchain đã lưu, có thể build lại đúng image đó. Điều này cần thiết khi phải điều tra lỗi trên một thiết bị đang chạy phiên bản cũ. Release note đi kèm liệt kê tính năng mới, lỗi đã sửa, lỗi đã biết, và các bước cần làm khi nâng cấp từ bản trước.

Sau khi bán ra, một sản phẩm có thể cần được hỗ trợ trên nhiều phiên bản cùng lúc. Linux kernel là một ví dụ: bên cạnh bản mainline, kernel duy trì các nhánh stable và longterm chỉ nhận bản vá được backport (xem Kernel Release Process).

Phần mềm theo các giai đoạn phát triển phần cứng

Với sản phẩm có phần cứng riêng, tiến độ phần mềm gắn với các mốc của phần cứng. Định nghĩa các giai đoạn EVT, DVT, PVT nằm ở trang Quy trình phát triển sản phẩm phần cứng; mục này chỉ trình bày phần mềm cần có ở mỗi giai đoạn. Bảng dưới là một cách phân chia tham khảo; mỗi tổ chức đặt tiêu chí riêng cho từng mốc.

Giai đoạn phần cứng Phần mềm cần có
Trước khi có board (thiết kế, prototype) BSP sơ bộ chạy trên board phát triển cùng SoC; driver cho các linh kiện mới được viết và thử trên module đánh giá (evaluation module)
EVT Bootloader và kernel khởi động được trên board thật; công cụ test từng interface (bộ nhớ, bus, cổng mạng) để phục vụ bring-up và kiểm chứng thiết kế
DVT Tính năng gần đầy đủ, BSP ổn định; phần mềm dùng cho test độ tin cậy, test môi trường và thử nghiệm chứng nhận
PVT Phần mềm cho nhà máy: image nạp tại dây chuyền, phần mềm test sản xuất, ghi thông tin riêng của từng thiết bị (số serial, địa chỉ MAC, khóa) như mô tả ở trang Provisioning & Device Identity; bản release candidate của phần mềm sản phẩm
Sản xuất hàng loạt Bản release chính thức; cơ chế OTA Update để cập nhật thiết bị đã xuất xưởng

Phần mềm cho nhà máy và phần mềm sản phẩm là hai sản phẩm khác nhau. Phần mềm test sản xuất cần chạy nhanh, kiểm tra được từng linh kiện và ghi kết quả về hệ thống của nhà máy; nó thường được phát triển song song từ giai đoạn DVT để kịp cho PVT.

Liên quan

Tài liệu tham khảo