Tư duy trong ngôn ngữ Swift, Phần 1: Giải cứu ponies
Tôi thấy rằng, những-người-mới-tìm-hiểu về Swift thường hay cố chuyển đổi code của họ từ ObjC sang Swift. Nhưng phần khó nhất để code trong Swift không phải là về cú pháp mà là cách bạn tư duy trong Swift, sử dụng những khái niệm mới trong Swift mà không có trong ObjC.
Trong series bài này, tôi sẽ lấy một ví dụ về code ObjC rồi chuyển đổi sang Swift và giới thiệu nhiều khái niệm của Swift trong quá trình chuyển đổi.
Phần 1 nói về: optionals, forced-unwrapped optionals, ponies, if let, guard, và 🍰.
ObjC code
Bạn muốn tạo ra danh sách các item (để hiển thị trong TableView chẳng hạn) - mỗi item có icon, title và url - nó được khởi tạo từ JSON. Đây là code ObjC:
1 |
|
Chuyển đổi trực tiếp sang Swift
Với những-người-mới-tìm-hiểu Swift, đây là cách chuyển đổi code sang Swift thường thấy:
1 |
|
Với những-người-có-nhiều-kinh-nghiệm trong Swift, họ sẽ thấy rất nhiều vấn đề xuất hiện trong đoạn code trên.
Có vấn đề gì với đoạn code trên?
Vấn đề là, sử dụng implicitly-unwrapped optionals
(value!), force-casts
(value as! String) và force-try
(try!) ở mọi nơi. Đây là thói quen rất xấu của những-người-mới-tìm-hiểu Swift.
Optionals là những người bạn: vì họ ép buộc bạn phải suy nghĩ về những trường hợp mà values là nil và nên làm gì trong hoàn cảnh đó (khi values là nil). Ví dụ như: “Tôi nên hiển thị cái gì khi không có icon? Tôi có nên sử dụng placeholder trong TableViewCell?”.
Đây là những trường hợp mà chúng ta thường lãng quên khi viết code ObjC, nhưng Swift giúp chúng ta không bỏ quên các trường hợp bị nil này. Vì vậy, bằng cách dùng force-unwrapping
, chúng ta đã không sử dụng lợi thế này của Swift và làm cho code-của-bạn bị crash khi nó là nil.
Không bao giờ
force-unwrapping
một value, trừ khi bạn thực sự biết những gì bạn đang làm. Hãy nhớ rằng mỗi khi bạn thêm!
chỉ vì compiler gợi ý, bạn đang giết chết một pony 🐴. [0]
Điều đáng buồn, những sai lầm này được khuyến khích bởi Xcode, bởi vì khi error thông báo: value of optional type ‘NSArray?’ not unwrapped. Did you mean to use ! or ?
, và đề nghị sửa chữa bằng cách thêm một dấu !
ở cuối 🙀.
Giải cứu ponies
Làm thế nào để tránh được dấu !
ở mọi nơi? Sau đây là một số cách:
- Sử dụng optional binding
if let x = optional { /* use x */ }
- Sử dụng
as?
thay vìas!
, trả về nil nếu cast fails; có thể sử dụng kết hợp vớiif let
- Sử dụng
try?
thay vìtry!
, trả về nil nếu expression failed [1]
Hãy xem code của chúng ta sau khi sử dụng những quy tắc trên [2]:
1 |
|
Pyramid of doom (Kim tự tháp chết chóc)
Một vấn đề xuất hiện, là khi thêm if let
ở khắp mọi nơi, dẫn đến là quá nhiều if
lồng vào nhau, đây là pyramid of doom (kim tự tháp chết chóc).
Một vài cách có thể giúp giảm bớt tình huống này:
- kết hợp nhiều lệnh
if let
vào làm mộtif let x = opt1, y = opt2
- sử dụng lệnh
guard
, cho phép ta giải quyết sớm 1 function nếu điều kiện không thỏa mãn, tránh được phần còn lại của function.
1 |
|
Lệnh guard
rất tuyệt. Vì ở phần đầu của function, nó giúp ta tập trung kiểm tra các biến-đầu-vào hợp lệ và trong phần còn lại, không cần bận tâm đến việc kiểm tra nữa. Nếu biến-đầu-vào không như mong đợi, chúng sẽ được giải quyết sớm và chúng ta chỉ tập trung vào phần chúng ta mong đợi ở sau.
Không phải Swift nhỏ gọn hơn ObjC sao?
Đúng là code trong Swift có vẻ phức tạp hơn so với ObjC. Đừng lo, chúng ta sẽ làm cho nó nhỏ gọn hơn trong phần 2.
Nhưng quan trọng nhất, code Swift này an toàn hơn nhiều so với ObjC. Trong thực tế, code ObjC ngắn hơn chỉ vì chúng ta quên để thực hiện rất nhiều các bài test an toàn. Thậm chí nếu nhìn có vẻ khá phổ biến, code ObjC có thể sẽ bị crash trong một số trường hợp, nếu như chúng ta đưa cho nó một JSON không hợp lệ, hoặc một trong số đó không được cấu trúc như một array of dictionary of strings (ví dụ: nếu ai đó tạo JSON và nghĩ rằng “icon” chính là kiểu Boolean(vì nghĩ nếu item có icon hoặc không), thay vì kiểu string …). Chỉ đơn giản, chúng ta quên xử lý tất cả những trường hợp này trong ObjC, vì ObjC không giúp chúng ta suy nghĩ về những trường hợp này trong khi Swift buộc chúng ta phải xem xét.
Vì vậy, code ObjC sẽ ngắn hơn bởi vì chúng ta đã bỏ qua một số thứ. Làm cho code ngắn hơn nhưng phải đánh đổi bằng việc code có thể bị crash.
Kết luận
Swift được thiết kế để an toàn hơn. Đừng bỏ qua optionals bởi force-unwrapping
: khi bạn nhìn thấy một !
trong code Swift, bạn biết rằng nó có thể là một code tệ hại và cái gì đó sai sai ở đây.
Trong phần 2 của series này, chúng ta sẽ xem thử làm thế nào cho code Swift ngắn gọn hơn và tiếp tục tư duy trong Swift bằng cách thay thế vòng lặp for
và if let
bằng map
và flatMap
.
[0]. Chú thích của người dịch: Pony ở đây là tác giả ví code của bạn chạy như ngựa. Việc giết chết ponies là tác giả muốn ám chỉ việc sử dụng dấu !
bừa bãi mà không suy nghĩ thông qua các cách sau: implicitly-unwrapped optionals
(value!), force-casts
(value as! String) và force-try
(try!) khiến code của bạn có thể crash bất cứ lúc nào.↩
[1]. Lưu ý rằng try?
âm thầm loại bỏ các error: khi sử dụng nó, bạn không thể biết tại sao code failed. Vì vậy, để tốt hơn hãy sử dụng do { try … } catch { }
thay vì try?
nếu có thể. Trong trường hợp này, chúng ta muốn trả về một mảng rỗng nếu quá trình phân tích JSON failed dù bất kỳ lý do gì, dùng try?
ở đây là ok. ↩
[2]. Như bạn thấy, tôi đã giữ lại 1 as!
ở trong đoạn code items.copy() as! NSArray
. Đôi khi nó có thể giết chết 1 pony force-cast, nếu bạn phải thực sự hiểu rằng kiểu trả về ở đây không thể khác được nữa, giống như kiểu mutableArray.copy()
. Trường hợp ngoại lệ như ở đây là rất hiếm, bạn luôn nghĩ đến cách sử dụng as?
đầu tiên. ↩