Nếu không làm gì cả, ít nhất hãy làm đúng cách để không làm gì
(devblogs.microsoft.com)- Ngay cả khi không thể cung cấp chức năng thực tế vì một nền tảng cụ thể hoặc trạng thái đã ngừng hỗ trợ, để các ứng dụng hiện có không bị hỏng, cần thiết kế hành vi “không làm gì” trong phạm vi hợp đồng API đã được tài liệu hóa
- Trong môi trường không có hạ tầng in ấn như Xbox, phản hồi thành công với 0 máy in sẽ an toàn hơn cho các ứng dụng được viết theo giả định trên PC so với
NotSupportedException - Cách này là inert API: thiết kế giữ nguyên bề mặt API và đặc tả, nhưng thực tế không thực hiện công việc hữu ích nào
- Với API widget đang bị ngừng hỗ trợ, nếu
CreateWidgetthành công nhưng trả vềnullptr, bên gọi sẽ gặp trạng thái mâu thuẫn; vì vậy đường dẫn thất bạiERROR_CANCELLEDđã được tài liệu hóa sẽ nhất quán hơn - Một inert API đúng cách chỉ làm biến mất chức năng, đồng thời dùng các đường dẫn thất bại mà mã hiện có đã chuẩn bị để giảm ngoại lệ, handle sai và crash
Ngay cả khi không còn chức năng, hợp đồng API vẫn còn
- Có những trường hợp phải làm cho API không làm gì cả, nhưng trạng thái không hoạt động đó cũng phải phù hợp với mã hiện có và hành vi đã được tài liệu hóa
- Một thiết kế không hoạt động sai có thể tạo ra ngoại lệ, giá trị trả về mâu thuẫn hoặc trạng thái không hợp lệ, dẫn đến ứng dụng crash hoặc lỗi người dùng không cần thiết
Cách vô hiệu hóa API in ấn trên Xbox
- Windows có hạ tầng in ấn rộng khắp, nhưng Xbox không có hạ tầng đó
- Nếu một ứng dụng cố gắng in trên Xbox và hàm in ném
NotSupportedException, vấn đề có thể trở nên nghiêm trọng hơn- Các ứng dụng được cài trên Xbox nhiều khả năng chủ yếu đã được kiểm thử trên PC
- Trên PC, việc in luôn khả dụng, nên một ngoại lệ không được xử lý có thể khiến ứng dụng crash
- Ngay cả khi bắt ngoại lệ, người dùng có thể thấy thông báo lỗi như “hãy cung cấp incident code cho đội hỗ trợ”
- Thiết kế tốt hơn là hàm in thành công nhưng báo rằng không có máy in nào được cài đặt
- Ứng dụng mở UI chọn máy in và hiển thị danh sách trống
- Người dùng thấy rằng không có máy in và có thể hủy yêu cầu in
- Với ứng dụng cố hỗ trợ cài máy in, hàm cài đặt có thể trả về ngay với mã kết quả có nghĩa là người dùng đã hủy thao tác
Điều kiện của inert API
- Hành vi “không làm gì” như vậy được gọi là inert
- Inert API là API mà bề mặt API vẫn tồn tại nguyên vẹn và hoạt động đúng theo đặc tả, nhưng thực tế không làm công việc hữu ích nào
- Điểm cốt lõi là không làm gì theo cách nhất quán với tài liệu và ít có khả năng gây vấn đề nhất cho mã hiện có
- Với API in ấn, cũng có thể thêm một hàm kiểm tra xem bản thân việc in có khả dụng hay không
- Ứng dụng có thể dùng hàm này để ẩn nút Print trên các hệ thống hoàn toàn không hỗ trợ in
- Ngay cả các ứng dụng chỉ đơn giản giả định có thể in cũng sẽ hoạt động như trong “một hệ thống không có máy in và việc thử cài đặt cũng không có tác dụng”
Thiết kế cần tránh trong API widget đang ngừng hỗ trợ
- API thuộc diện ngừng hỗ trợ có một hàm tạo handle widget, một hàm nhận handle widget và một hàm đóng handle widget
- Đề xuất ban đầu là để
CreateWidgettrả vềS_OKđồng thời đặt*widget = nullptr- Lời gọi thành công nhưng không nhận được handle hợp lệ
- Trong mã kiểm thử thực tế, có đoạn xác định thành công hay không dựa trên việc handle có
nullhay không, chứ không dựa trên giá trị trả về
- Thiết kế để
EnableWidgettrả về “handle không hợp lệ” cũng khiến bên gọi bối rối- Ứng dụng truyền handle nhận được sau khi
CreateWidgetthành công vàoEnableWidget - API đáp rằng handle đó không hợp lệ
- Bên gọi gặp trạng thái mâu thuẫn: “đã yêu cầu widget và đã nhận được, nhưng khi đưa lại thì bị bảo đó không phải widget”
- Ứng dụng truyền handle nhận được sau khi
Thiết kế được sửa bằng đường dẫn thất bại đã được tài liệu hóa
- Tài liệu hiện có có giá trị trả về
ERROR_CANCELLED, mang nghĩa người dùng đã hủy việc tạo widget - Ứng dụng vốn đã phải xử lý khả năng widget không được tạo do điều kiện bên ngoài, nên có thể làm cho việc tạo widget luôn thất bại dưới dạng người dùng hủy
- Luồng của thiết kế đã sửa rất đơn giản
CreateWidgetđể*widget = nullptrvà trả vềHRESULT_FROM_WIN32(ERROR_CANCELLED)- Vì việc tạo widget luôn thất bại, không tồn tại handle widget hợp lệ nào
GetWidgetAliases,EnableWidget,Closetrả vềE_HANDLE, nghĩa là handle không hợp lệ
- Theo cách này, vì không có widget, cũng không cần tạo alias giả
- Do không tồn tại widget hợp lệ, cũng không có trường hợp ứng dụng có thể yêu cầu alias một cách bình thường
Tiêu chí khi đưa API desktop sang môi trường khác
- Trên desktop, API in ấn luôn tồn tại, và hàm “lấy danh sách máy in” được tài liệu hóa là không ném ngoại lệ
- Khi đưa API này sang Xbox, nếu muốn các ứng dụng desktop hiện có tiếp tục chạy, hành vi inert trả về một cách trung thực rằng “Xbox không có máy in” là phù hợp
- Ném ngoại lệ cho câu hỏi “có bao nhiêu máy in?” không phù hợp với hợp đồng API đã được tài liệu hóa
- Nguyên tắc tương tự cũng áp dụng khi ngừng hỗ trợ một API hiện có
- Dù không làm công việc hữu ích, hành vi của API vẫn phải nhất quán với hợp đồng
- Điều quan trọng là dùng các giá trị trả về và trạng thái mà mã hiện có đã có thể xử lý
2 bình luận
Ý kiến trên Lobste.rs
Nói đùa vậy thôi, tôi không đồng ý với kiểu lập trình và trải nghiệm người dùng phòng thủ quá mức như thế này. Làm vậy thì phần mềm sẽ không làm đúng việc của nó mà người dùng cũng không biết vì sao, lại càng không có cách nào để tìm ra. Ứng dụng nên bắt lỗi và nếu có thể thì tạo thông báo thân thiện với người dùng, nếu không thì ít nhất cũng phải hiển thị thông báo lỗi gốc cho người dùng. Nếu là tác vụ nền thì phải có nhật ký lỗi
Tôi công nhận bài này được viết từ góc nhìn của nhà phát triển API chứ không phải nhà phát triển ứng dụng. Vậy thì cần tài liệu hóa lỗi API và cung cấp thông báo lỗi để phía gọi có thể xử lý
Tôi cũng không thích việc ẩn nút trong UI chỉ vì không có quyền truy cập. Nếu không gian cho phép thì tốt hơn nên hiện nút nhưng vô hiệu hóa nó, và khi người dùng rê chuột vào thì hiển thị thông điệp cho biết làm thế nào để kích hoạt nó
Nhìn chung, yêu cầu tính chính xác vẫn tốt hơn. Tuy nhiên, nếu bạn đã có 1 tỷ người dùng hiện hữu thì việc tránh làm hỏng họ nhiều nhất có thể là lựa chọn rất khôn ngoan, và từ góc nhìn người dùng thì cứ chạy được là tạo ra giá trị thực ở cấp hệ thống. Cuối cùng, thái độ nên là thất bại sớm, nhưng đừng để thất bại hàng loạt
Chuyện này gần với độ ổn định ABI hơn là API. Trên Windows, phần mềm được build từ 15 năm trước vẫn phải tiếp tục chạy được trên hệ điều hành mới càng nhiều càng tốt. Bạn không thể thay đổi chữ ký hàm, nên nếu muốn những API không còn ý nghĩa nữa vẫn tiếp tục hoạt động thì phải dùng những lời nói dối thiện ý
Ví dụ, API vẫn giả vờ như Active Desktop còn tồn tại, vì lựa chọn khác là làm hỏng hàng loạt phần mềm cũ đang tồn tại
Khi đó bạn không thể biết là tính năng ấy đã biến mất hay chỉ bị chôn ở một màn hình khác trong lúc đó
Nhưng nếu ứng dụng không làm vậy, thì người dùng ứng dụng đó sẽ đổ lỗi cho Windows. Họ đổ lỗi cho Windows chứ không phải cho ứng dụng, và ngay cả khi ứng dụng bị crash cũng thế
Vì vậy Microsoft mới tạo ra các giải pháp vòng vo. Cứ để tác vụ in tự biến mất thì dễ hơn nhiều, và rồi người dùng sẽ nghĩ một lát rồi chấp nhận rằng “à đúng rồi, mình đâu có máy in”
ifđể kiểm tra null. Mỗi lần thấy vậy tôi lại nhớ đến bài nàyVì thế tôi đã bảo agent đừng lặp đi lặp lại kiểm tra null, mà hãy dùng các hàm vô hại và chỉ xác nhận một lần tại thời điểm khai báo rằng giá trị chắc chắn không bao giờ là null
Các ý kiến trên Hacker News
Tôi học được rằng đây là cách nuốt lỗi, và cho rằng đó là một thực hành không tốt
Nó không chỉ không giải quyết được vấn đề thực tế là không thể in trên Xbox, mà còn che giấu mức độ hỏng hóc của phần mềm, khiến việc phát hiện lỗi và kiểm thử khó hơn nhiều
paniccủa Go tốt ở chỗ nó không được tạo ra để bị lạm dụng hoặc phục hồi trong runtime, mà là công cụ hú còi thật to ở thời điểm kiểm thử để báo rằng lập trình viên đã làm saiNếu các tác nhân trong hệ thống chủ động che giấu khiếm khuyết hoặc lỗi của chính mình, việc tìm ra nguyên nhân và sửa chữa sẽ khó lên theo cấp số nhân
Trong các hệ thống B2B, microservice, lấy API làm trung tâm mà tôi xử lý, các yêu cầu ngoài đặc tả nên được cho fail nhanh
Nhưng Windows đã phải uốn mình rất nhiều để giữ tương thích, và còn có những trường hợp cứu phần mềm nổi tiếng bị lỗi như SimCity bằng cả cách vá bộ nhớ: https://arstechnica.com/gadgets/2022/10/windows-95-went-the-...
Trong môi trường desktop nơi người dùng không thể sửa mã hệ thống, trải nghiệm người dùng cuối trong nhiều trường hợp quan trọng hơn tính đúng đắn
Microsoft đã học được rằng lý do gây BSOD không quan trọng; phần mềm hay phần cứng tệ vẫn làm xấu đi danh tiếng của Microsoft
Cá nhân tôi thích thiết kế fail nhanh hoặc dừng ngay khi gặp đầu vào hay điều kiện sai, nhưng cũng hiểu giá trị của cách tiếp cận này trong môi trường đó
Điều quan trọng là ngữ cảnh, chứ không phải áp dụng một phía nào đó một cách giáo điều
Ví dụ, không nên mang cách tiếp cận của Chen vào thiết kế lò phản ứng hạt nhân hay giao dịch tần suất siêu cao, còn với engine game thì đó có thể là một cách rất hay
Việc quyết định có để một thành phần runtime ở trạng thái “vô hại nhưng bất hoạt” hay không là một quyết định thiết kế có trước chuyện phần triển khai có
panichay khôngTrong trường hợp này, việc định nghĩa “in trên Xbox” nghĩa là gì thuộc về Microsoft
Có thể định nghĩa đó là lỗi, hoặc về mặt lý thuyết có thể định nghĩa là một chức năng sau này hỗ trợ được nhưng hiện chưa có cách thực hiện thực tế
Nếu cắm máy in USB và “game” biết cách in, chẳng hạn như cổng mở rộng của trò chơi Gameboy từng hỗ trợ GB Printer, thì về lý thuyết Xbox cũng có thể in
Việc một game Xbox thử in không nhất thiết là lỗi về bản chất; định nghĩa nó là lỗi cũng chỉ là một lựa chọn có các đánh đổi như tính di động của ứng dụng
Có thể hiện màn hình chọn nhưng không có máy in nào để chọn như trên Xbox, có thể khiến API nói dối rằng đã in, hoặc cũng có thể hỗ trợ in thật
Chỉ sau khi đã định nghĩa ý nghĩa của “in trên Xbox” là lỗi, việc lỗi đó không được ném ra mà bị nuốt mới trở thành vấn đề triển khai hay gỡ lỗi
Có hai cách xử lý khả dĩ: báo lỗi vì máy chắc chắn không bao giờ có máy in, hoặc trả về danh sách máy in rỗng vì máy chắc chắn không bao giờ có máy in
Cả hai đều hợp lý, nhưng chỉ có một cách là không phá vỡ user space
“Hú còi thật to” thì tốt cho tester hay developer, nhưng không hay lắm với người dùng cuối
Với người dùng cuối, nuốt lỗi vẫn tốt hơn là ứng dụng chết
Không thể biết cái gì đã hỏng: phần mềm trên máy tính của tôi, phần cứng, cáp Ethernet hay Wi‑Fi, switch, router, modem cáp, đường cáp Internet kéo tới nhà, ISP, hay hệ thống từ xa mà tôi đang cố truy cập; chuyện này chẳng được cải thiện chút nào
Việc phản ứng tiêu cực là có thể đoán trước, nhưng chính những chuyện bừa bộn như thế này là lý do khi bạn bấm vào tài liệu Word ’97 hay một trò chơi được biên dịch cho MS-DOS từ 30 năm trước, nó vẫn mở như bạn kỳ vọng
Tương thích ngược luôn messy, và chỉ có hai lựa chọn: làm không hoàn hảo, hoặc không làm gì cả
Cuối cùng phải dùng GOG, và GOG rốt cuộc cũng ảo hóa môi trường
Ví dụ, tôi không nghĩ sẽ có ai cài được bản Sim City gốc trên Windows 11 thành công
Định dạng phụ thuộc vào driver máy in đã cài
Trong khi đó, tệp LaTeX từ 35 năm trước đến nay vẫn chạy tốt
Word từng không thể đảm bảo rằng định dạng sẽ không thay đổi ngay cả khi mở lại trên cùng máy tính và cùng phiên bản, nên tôi đã học TeX
Game cũng vậy, Microsoft đã làm hỏng vô số game khi đóng GFWL
Bản Dark Souls gốc không thể chơi được cho đến khi được phát hành lại trên Steam, nên phải dùng bản lậu
Một hộp thoại “không được hỗ trợ” sẽ hiện ra, và bạn có thể tải dosbox, nhưng như thế không phải là “đúng như kỳ vọng”
Gần như lần nào cũng phải bỏ cuộc hoặc mua lại trên GOG; thực chất là trả tiền cho công sức đã dọn dẹp các vấn đề tương thích
Không gì khó chịu bằng một UI thiết bị ám chỉ rằng những thiết bị đó có thể thực sự tồn tại, nhưng hiện tại lại báo là không có
Rốt cuộc phải mất thời gian mới phát hiện ra rằng các thiết bị đó không được hỗ trợ, và màn hình kia chỉ là một mockup do ai đó dựng ra
Nếu ứng dụng không chuẩn bị cho trường hợp không hỗ trợ in/xuất mà thao tác xuất lại ném exception, ứng dụng có lẽ sẽ chết ngay
Một ứng dụng có chuẩn bị cho việc không hỗ trợ in/xuất thì nên dùng API tường minh để kiểm tra trước xem có hỗ trợ hay không, và nếu không được hỗ trợ thì không nên hiển thị UI in/xuất
Bài viết không nói về loại ứng dụng đó, mà nói về cách xử lý những ứng dụng không kiểm tra trước
Lỗi nằm ở ứng dụng, nhưng một nền tảng tốt nên cố gắng làm cho cả ứng dụng tệ vẫn chạy được, thay vì cứ để nó chết
Vai trò của Microsoft là đảm bảo ứng dụng không chết chỉ vì nhà cung cấp phần mềm đã không tính đến máy in
Việc khi ứng dụng muốn in thì yêu cầu người dùng chọn máy in rồi hiển thị một danh sách trống — có vẻ Microsoft đã không học được gì trong 30 năm: https://www.reddit.com/r/hacking/comments/djvzd/windows_nt_l...
Hướng cụ thể mà bài viết đề xuất, tức là trước khi người dùng chịu khổ thì thành phần phải chịu khổ, là đúng, nhưng cách framing rất khó chịu
Những câu kiểu “có lúc nên làm cho API không làm gì cả”, “hàm in/xuất ném
NotSupportedExceptionlà sai” tuyệt đối không phải lời khuyên chungĐây là một hack để hỗ trợ các client tệ, đôi khi cần thật, nhưng không phải lời khuyên bình thường hay có thể khái quát hóa
Cách diễn đạt này cho thấy dấu vết của việc nội tâm hóa nỗi khổ trong vai trò lập trình viên Microsoft
Nếu client không thay đổi mà API lại làm hỏng nó, thì thứ tệ không phải client mà là API
Với mọi nền tảng được chấp nhận rộng rãi và xem trọng tương thích ngược, đây là việc hoàn toàn bình thường và có thể khái quát hóa
Windows là ví dụ tiêu biểu, nhưng chuyện tương tự cũng xảy ra với Linux kernel ABI, glibc, nền tảng web, Java, v.v.
Không thể quay ngược thời gian để biên dịch lại hoặc viết lại client
Nếu client đã kiểm tra
NotSupportedExceptionthì bạn nên trả về nó, nhưng nếu không thì cần cách tiếp cận khácĐây là Windows
Không được làm hỏng các ứng dụng mà người dùng phụ thuộc vào
Nếu có ứng dụng client “tệ” hoặc hoạt động sai, bạn phải dùng mọi workaround, shim, hack tương thích cần thiết để làm cho nó hoạt động như kỳ vọng
Kể cả khi phải thêm mã quản lý bộ nhớ đặc biệt để SimCity chạy được cũng vậy
Khách hàng của bạn đang phụ thuộc vào client đó, và nếu nó dừng lại vì thay đổi của bạn, họ sẽ trách bạn
Ở đây mọi người đều bám vào “không làm gì cả” và “xử lý lỗi”, nhưng có vẻ bài viết chưa giải thích tốt rằng nó không kêu gọi nuốt mọi vấn đề rồi cùng hát kumbaya
Bối cảnh thực sự là giả lập và tương thích cho phần mềm sẽ không thay đổi, và điều đó rất khác với hầu hết tình huống
Bản thân Microsoft cũng thường làm mờ ranh giới giữa hai thứ nên càng dễ gây nhầm lẫn, nhưng điều đang nói ở đây không phải xử lý lỗi thông thường
Giả lập về bản chất là nói dối
Xbox thực ra không có cách nào gắn máy in, bạn đang chạy game Windows trên Linux, và chiếc iPhone giả có thể thực chất là một máy chủ ARM của AWS
Nếu đã nói dối thì phải nói sao cho thuyết phục, để các ứng dụng hiện có tiếp tục hoạt động
Khi Microsoft nói có thể port ứng dụng Windows hiện có sang Xbox, trong ứng dụng Windows vốn có lời hứa rằng nó có thể in/xuất, nên Xbox phải nói dối để làm cho việc đó trông có vẻ khả thi
Ngược lại, nếu chương trình đang use-after-free mà bạn âm thầm né đi như thể không có bug, đó không phải cách xử lý đúng
Vì không ai hứa rằng cả use-after-free hiện có cũng sẽ tương thích tới mức từng bug trên nền tảng mới
Đó chỉ là giả định vì cách diễn giải đó ủng hộ lập trường bạn muốn
Thực tế, nội dung bài viết vẫn áp dụng y nguyên khi ngày mai tôi viết một chương trình cho Xbox và muốn in/xuất
Không có phần nào phân biệt rằng hành vi này chỉ dành cho phần mềm cũ và không còn được bảo trì
Tôi vừa thích vừa không thích cách tiếp cận này
Theo bản năng, tôi không thích xử lý vấn đề bằng kiểu tuân thủ ác ý
Mặt khác, nếu mục tiêu là làm cho nhiều người dùng hơn chạy được nhiều phần mềm hơn trên nền tảng, dù chức năng in/xuất bị hỏng, thì tôi hoàn toàn đồng ý rằng đó là quyết định tốt
Luồng bình luận này thật kỳ lạ
Rõ ràng có những người ghét bất cứ thứ gì Microsoft làm
Trọng tâm của blog là làm cho quá trình đưa ứng dụng (UWP) lên XBOX diễn ra không ma sát
Không cần biên dịch lại, không cần
ifdefbẩn, không cần thêm việc; ứng dụng “cứ chạy”, rồi sau đó lập trình viên có thể điều chỉnh ứng dụng cho phù hợp với nền tảng XBOXĐiều quan trọng là lập trình viên đã có thể đưa ứng dụng lên XBOX mà không tốn công sức, và điều này tốt cho cả nền tảng lẫn lập trình viên
Với người dùng, danh sách máy in trống là tín hiệu rõ ràng rằng không thể in
Quan trọng nhất là ứng dụng không chết hoặc hiển thị lỗi tệ nhất kiểu “Đã xảy ra sự cố. Vui lòng thử lại sau”
Thông báo đó không phải trải nghiệm người dùng tốt hơn
Lập trình viên có thể biên dịch lại ứng dụng và thêm thông báo giải thích rõ hơn, nhưng như vậy lại quay về điểm xuất phát
Ví dụ, chỉ trích rằng nó nuốt lỗi hoàn toàn không trúng trọng tâm
Điểm cốt lõi mà phía phê phán bài viết này bỏ sót là ở đây không có tình huống ngoại lệ nào cần phải ném exception
Thiết bị này không có máy in nào được kết nối, và trong trường hợp này theo định nghĩa là như vậy, nên ứng dụng phải xử lý tình huống đó một cách gọn gàng chứ không được chết
Không thể vì laptop không truy cập được máy in mà ném exception
Sau khi thêm máy in trong phần cài đặt, nếu chuyển sang tab “Color Profiles” trong lúc đang cài đặt, đúng thời điểm thì gnome-control-center sẽ chết
Nhìn kỹ thì nó đã cố liệt kê các máy in khả dụng; nó biết là phải có máy in, nhưng thông tin chưa tồn tại nên cứ thế chết
May mắn là chỉ cần đợi vài giây cho đến khi GNOME hoàn tất cài đặt máy in rồi mở lại phần cài đặt là được
Dù vậy, nếu là ứng dụng cốt lõi giúp người dùng cuối từ nhiều nền tảng khác nhau dùng desktop cho đúng, thì trong một thế giới lý tưởng nó tuyệt đối không nên chết
Nghĩ đến các điều kiện biên đã khó, tưởng tượng rằng bản thân việc xử lý điều kiện biên cũng có những điều kiện biên khác còn khó hơn
Đúng hay sai thì ít nhất tác giả cũng đang suy nghĩ sâu hơn câu “đây là app tệ, nên viết lại để hỗ trợ Wayland”
Đã từng có thời việc trình duyệt cố gắng hết sức để hiển thị trang dù mã HTML có lỗi được xem là một chiến lược tuyệt vời
Kiểu như đoán ý định của tác giả càng nhiều càng tốt rồi tiếp tục, vì lỗi là xấu và người dùng không muốn thấy lỗi
Tôi tưởng chúng ta đã học được từ trải nghiệm đó, nhưng có vẻ là không
Nỗ lực XHTML nhằm thay thế bằng kiểm chứng nghiêm ngặt đã thất bại; thay vào đó, HTML5 thành công đã quay lại và định nghĩa rõ ràng cách diễn giải mọi chuỗi sai có thể có, để ít nhất các trình duyệt hành xử nhất quán
Nếu các trình duyệt thời đầu cứ gặp thẻ chưa từng thấy là hiển thị lỗi ngay, các tính năng trình duyệt mới đã bị bóp nghẹt từ lúc mới ra đời
HellovàWorld!để xem nhờ trí tuệ chúng ta thu được, giờ có lỗi hiện ra hay khôngNhưng nếu nó vẫn render ổn, điều đó có nghĩa là vẫn còn nhiều thứ để học, hoặc cũng có thể là việc xử lý một số loại lỗi một cách mềm mại và chỉ chết trong các tình huống ngoại lệ là có ý nghĩa
Có lẽ nếu quay về năm 1999 cũng chẳng tiếc nuối gì nhiều