Month: January 2021

  • Swift – Basic to advanced Closure

    Swift – Basic to advanced Closure

    Với nhiều bài toán, không phải lúc nào cũng đơn giản. Ví dụ chỉ với 2 số nguyên, thực tế có rất nhiều công thức áp dụng được với 2 số này, từ đơn giản như cộng, trừ, nhân, chia … đến phức tạp như hàm mũ, khai căn,… Nếu chỉ sử dụng cách định nghĩa sẵn các function ta không giải quyết tất các các case của bài toán. Giải pháp ở đây là chúng ta sử dụng closure.


    Đầu mục bài viết

    Closure cơ bản

    1. Tạo closure cơ bản
    2. Tạo tham số cho closure
    3. Trả về giá trị từ closure
    4. Truyền closure như 1 tham số vào Function

    Closure nâng cao

    1. Truyền closure với nhiều tham số vào Function
    2. Trả về closure từ functiion
    3. Lưu trữ dữ liệu với closure

    Các kiểu gọi hàm với closure

    Tổng kết


    Basic closure

    1. Tạo closure cơ bản

    Swift cho phép chúng ta sử dụng function như bất kỳ kiểu dữ liệu nào,ví dụ như string, integers,… Điều này có nghĩa rằng chúng ta có thể tạo ra 1 function và gán nó cho một biến, gọi function đó bằng cách sử dụng biến đó và thậm chí có thể gán function đó vào các function khác dưới dạng tham số.

    Function mà ta sử dụng theo cách này được gọi là closure, mặc dù cách hoạt động của nó giống function tuy nhiên cách viết khác nhau 1 chút.

    Ví dụ đơn giản để print 1 đoạn text :

    Ở đây mình tạo ra một function tuy nhiên không có tên, và gán nó cho biến driving. Ta có thể gọi driving() như thể nó là 1 hàm thông thường :

    2. Tạo tham số cho closure

    Khi khởi tạo closure, ta sẽ nhận ra nó không có tên nên sẽ không có bất kỳ vị trí nào để thêm tham số như function bình thường. Tuy nhiên không có nghĩa là closure không nhận tham số input, chỉ là nó có cách làm khác so với function: các tham số được liệt kê bên trong dấu {}.

    Cách tạo ra các tham số để closure có thể “chứa chấp” rất đơn giản, chỉ cần liệt kê chúng bên trong dấu ngoặc đơn ngay sau dấu ngoặc nhọn mở, sau đó thêm keyword in để Swift biết phần nào là phần bắt đầu closure.

    Ví dụ mình sửa driving() bên trên thành closure có chứa tham số

    Và một trong những điểm khác biệt nữa giữa closure và function là bạn không cần sử dụng tên tham số khi gọi closure.

    3. Trả về giá trị từ closure

    Closure có thể trả về giá trị, và cách viết tương tự như khai báo tham số: viết nó trong closure, ngay trước keyword in.

    Với closure driving() bên trên, mình sẽ trả về đoạn string kia thay vì print thẳng ra, để làm thế ta sử dụng → String trước keyword in, sau đó sử dụng return như function bình thường

    Bây giờ có thể gọi closure này và in ra giá trị String nó trả về

    4. Truyền closure như một tham số vào function

    Vì closure có thể được sử dụng như string, integer,…, bạn có thể truyền nó vào 1 function. Syntax của nó khá là rắc rối với newbie tuy nhiên nếu bạn đã hiểu về nó thì sẽ thấy không rắc rối lắm :))

    Đây là closure driving() gốc của chúng ta

    Nếu bạn muốn truyền closure này vào trong 1 function để nó có thể thực thi bên trong function đó, bạn phải chỉ định kiểu tham số là () -> Void.

    Ví dụ mình viết 1 function travel() mà nó nhận tham số là các kiểu action khác nhau

    Ta có thể gọi hàm travel() mà sử dụng closure driving

    Bên trên là 1 ví dụ về truyền closure như 1 tham số, tuy nhiên ta đang sử dụng () → Void, nói cách khác là không truyền vào tham số và cũng không nhận giá trị trả về.

    Tuy nhiên,1 closure vẫn có thể nhận tham số của chính nó khi chính closure đó đang là 1 tham số của function khác.

    Ví dụ, mình viết lại function travel() mà nó chỉ có 1 closure là tham số duy nhất, và closure đó nhận tham số là 1 String

    Và để thực thi function travel(), ta gọi nó với 1 tham số closure


    Advanced closure

    1. Truyền closure với nhiều tham số vào function

    Mình lại xin phép viết lại hàm travel() bên trên, tuy nhiên lần này hàm travel() của chúng ta sẽ cần 1 closure mà nó chứa một vài thông tin khác thay vì 1 string như bên trên, cụ thể nó sẽ chứa thông tin về địa điểm mà một người sẽ đến và tốc độ họ đi. Lúc này chúng ta cần sử dụng (String, Int) → String cho kiểu tham số của closure

    Để thực thi function travel(), ta gọi function này với tham số closure được truyền vào

    ( Bạn có thể thấy lạ với các keyword $0 và $1, hiện tại bạn không hiểu cũng không sao cả vì mình sẽ giải thích rõ hơn ở dưới =]] )

    Closure giống với function là nó có thể nhận bao nhiêu tham số cũng được, tuy nhiên bạn có thể thấy rằng một function mà nhận quá nhiều tham số thì sẽ rất khó hiểu dẫn đến confuse, điều này còn kinh khủng hơn với closure khi bản chất closure cũng đã rất phức tạp rồi. Vì thế để mọi thứ clear bạn nên chỉ sử dụng từ 1 đến 3 tham số  mà thôi.

    2. Trả về closure từ function

    Tương tự như việc bạn truyền tham số closure vào function, bạn cũng có thể nhận về 1 closure mà được trả về từ function.

    Syntax để return closure từ function có hơi rắc rối 1 chút, bởi vì nó dùng → 2 lần: một để chỉ định giá trị trả về của function và một để chỉ định giá trị trả về từ closure.

    Mình lại viết lại hàm travel() mà không nhận tham số, tuy nhiên lại có trả về 1 closure mà closure nhận tham số là String và trả về Void

    Chúng ta gọi travel() để nhận về closure đó, sau đó gọi nó như 1 function

    Còn 1 cách gọi nữa để gọi trực tiếp giá trị trả về từ travel() – cách này không được khuyến khích sử dụng:

    3. Lưu trữ dữ liệu với closure

    Nếu bạn sử dụng bất kỳ giá trị bên ngoài nào trong closure, Swift sẽ lưu và giữ chúng cùng closure, vì thế giá trị này có thể bị thay đổi kể cả nó không còn tồn tại.

    Ví dụ, việc lưu trữ giá trị trong closure xảy ra khi ta tạo 1 giá trị trong hàm travel() mà giá trị đó được sử dụng trong closure, ở đây ví dụ như ta muốn kiểm tra xem closure được gọi trả về bao nhiêu lần

    Mặc dù biến counter được tạo bên trong travel(), nó sẽ được lưu trữ bởi closure vì thế nến nó sẽ vẫn luôn tồn tại cho closure đó.

    Vì thế nếu ta gọi result(“London”) nhiều lần, biến đếm sẽ luôn tăng lên :


    Các kiểu gọi hàm với closure

    Ví dụ, ở đây định nghĩa 1 functiontypecalculationresultcallback với tham số đầu vào là 1 kiểu int và không có giá trị trả về, chúng ta cũng có thêm 1 hàm là multiplyNumber với 2 tham số nhận vào là kiểu int, không có giá trị trả về và kèm theo 1 tham số có tên là callback có kiểu là CalculationResultCallback. Trong ngôn ngữ lập trình nói chung, callback có nghĩa là lời gọi hàm sau, tức là sau quá trình sử lý logic và ra được 1 kết quả nào đó mà chúng ta cần xử lý thêm với kết quả đó.

    Hàm multiplyNumbers có ý nghĩa là, với 2 giá trị nguyên bất kỳ, nó sẽ tính phép nhân 2 số, sau đó sẽ trả về kết quả bằng 1 hàm mà ở đó, ta có thể tùy ý sử dụng kết quả theo ý ta muốn.

    Ta có các cách gọi hàm như sau:

    • Ta có thể thấy, 2 tham số đầu tiên có kiểu int được gọi hoàn toàn bình thường, nhưng với tham số thứ 3 với tên là callback, thay vì truyền vào 1 số, hoặc 1 tên hàm như trước, thì ta truyền vào hẳn 1 hàm,mà ở đó ta in ra dòng “Tích của 2 số là …”
    • Trong closure này,chúng ta có từ khóa in phân cách 2 nửa,bên trái là khai báo tham số nằm trong () và khai báo kiểu trả về là void,bên phải là lệnh được thực thi khi gọi đến closure này,toàn bộ closure được bao trong cặp dấu {}.
    • Giống như function bình thường,ta có thể bỏ void nếu hàm không trả về kết quả,và có thể bỏ đi luôn () bao tham số.
    • Đây chính là dạng gọi cơ bản nhất của closure
    • Swift quy định,với những hàm khai báo closure là tham số nhưng ở vị trí cuối cùng trong danh sách tham số: ví dụ như trên,ta có thể gọi hàm bằng cách gọi hàm với 2 tham số int bình thường,kéo theo 1 tham số closure được bao bằng dấu {},trường hợp này được gọi là Trailing closure
    • Để closure gọn hơn nữa, có 1 tính năng gọi là Shorthands, bản chất là thay vì khai báo tham số với tên biến cụ thể thì chúng ta sử dụng cú pháp $0,$1,$2,… để thay thế cho các tham số ở vị trí 0,1,2,.. Tất nhiên,nếu hàm chỉ có 1 tham số mà ta gọi đến $1 thì hàm sẽ báo lỗi.
    • Đối với shorthands, ta không thể khai báo tham số như những trường hợp trên nữa,đổi lại sẽ ngắn gọn hết sức có thể..
    • Tuy nhiên,với hàm có nhiều tham số,ta khó xác định $0,$1,$2,… là gì.Vì thế nên chỉ sử dụng shorthands với hàm đơn giản chỉ có từ 1-3 tham số mà thôi.

    Tổng kết

    Về bản chất

    • Closure là 1 function.
    • Nhưng là function không đầy đủ tên function và thân function , mà chỉ có mỗi thân function .
    • Mục đích của nó không phải gọi function bằng tên, mà là được chèn vào tham số của 1 function khác.
  • How to write clean code (P1)

    How to write clean code (P1)

    Mặc dù lâu không viết gì vì đang bận làm đồ án, nhưng sau khi khi hoàn thành 1 task về refactor vài nghìn dòng code đã là động lực để em phải ngoi lên viết bài viết này, để chia sẻ về cách viết code mà bản thân đang áp dụng.
    Phần 1 của bài viết sẽ giới thiệu về cách cải thiện code đơn giản và dễ dàng nhất: Đặt tên

    Nội dung bài viết

    • Tầm quan trọng của clean code
    • Meaningful names

    Tầm quan trọng của clean code

    Tại sao clean code lại quan trọng?

    • Những dòng code cũng chính là 1 bản design document, vì vậy nếu code được viết 1 cách gọn gàng, dễ hiểu thì khi 1 người mới đọc code thì sẽ dễ dàng nắm được logic, flow của code – Sếp HoaND1 said.
    • Việc viết code 1 cách clean sẽ giảm thiểu thời gian dể người khác, hoặc chính bạn sau 1 thời gian đọc lại, có thể nhanh chóng hiểu được; tránh gây ra những hiểu lầm về mặt logic.
    Clean code cũng giúp tránh việc đồng nghiệp của bạn phải thốt lên “WTF” nữa

    Meaningful names

    Việc chọn 1 cái tên sao cho truyền tải đủ ý định của biến, hàm, class, … đôi khi sẽ mất nhiều thời gian, những nó sẽ tiết kiệm được nhiều thời gian hơn so với thời gian bị mất.
    Một cái tên tốt nên thể hiện tại sao biến này, hàm này, … tồn tại, nó có tác dụng thế nào, được sử dụng thế nào mà không cần tốn thêm quá nhiều thời gian để đi tìm hiểu chúng làm gì.

    let temp: Int = 0
    func check() -> Bool {}

    Ví dụ với những cái tên kiểu này, đồng nghiệp của bạn sẽ phải lặn lội mọi ngóc ngách nơi biến, hàm được gọi để có thể hình dung 1 cái nhìn mơ hồ xem chúng có tác dụng gì… Hãy biết thương đồng nghiệp của bạn.
    Ok, giờ thì đi tìm hiểu 1 vài cách đặt tên cho tốt

    Coding Conventions

    Trong swift, có 1 vài coding conventions cơ bản trong việc đặt tên như:

    • Tên biến, tên class nên là danh từ, tên hàm nên bắt đầu bằng 1 động từ
    • Sử dụng camel case (tránh dùng snakeCase)
    • Viết hoa chữ cái đầu cho các kiểu dữ liệu type, protocol, viết thường cho các thứ khác. …

    Hãy chọn những cái tên cụ thể với hành vi

    func sortListObject() {}

    Như tên hàm này không được cụ thể lắm, vì qua cái tên không thể hiện được là object sẽ sort theo kiểu gì?
    Nếu sort theo tên thì nên sửa lại thành sortListObjectById() chẳng hạn, hoặc sortListObjectByName() nếu sort theo tên.

    Nếu có điều gì quan trọng về 1 biến, 1 hàm mà người đọc nên biết, thì nên thêm thông tin đó vào tên.

    var id: String // "af84ef845cd8"
    -> var hexID: String

    Tránh những cái tên chung chung

    Những cái tên mơ hồ như temp, tmp, i, j, … trong đa số trường hợp thường không thể hiện được quá nhiều thông tin. Vì vậy, hãy chọn 1 cái tên truyền tải nhiều ý nghĩa hơn thay vì chúng.

    Cũng có 1 vài trường hợp những cái tên chung chung có thể chấp nhận. Ví dụ như:

    if left < right {
       let temp = left
       left = right
       right = left
    }

    Trong những trường hợp như thế này, việc sử dụng tên “temp” cũng ổn. Bởi mục đích của nó là lưu trữ tạm thời,với thời gian tồn tại chỉ vài dòng, nó cũng không có nhiệm vụ nào khác. Nó không được chuyển sang chức năng khác hoặc được đặt lại, hoặc sử dụng nhiều lần. Và quan trọng hơn là người khác vẫn có thể dễ dàng hiểu.

    Hãy chọn những cái tên ý nghĩa. Nếu bạn định sử dụng những cái tên chung chung như “temp”, “tmp”, … hãy chắc chắn rằng có 1 lí do hợp lí cho việc đó.

    Tránh những tên quá dài

    • Khi chọn tên, nên tránh những cái tên quá dài, bởi vì chúng rất khó đọc và khó để nhớ
    newNavigationControllerWrappingViewControllerForDataSourceOfClass
    • Đôi khi những cái tên cũng chứa những thông tin bị thừa, mà nếu bỏ đi thì cũng không ảnh hưởng gì tới ý nghĩa.
    func convertToString() 
    func toString() // Vẫn truyền tải đủ ý nghĩa

    Tránh những cái tên hiểu lầm

    • Tránh đặt những tên kiểu getXYZ(), sizeXYZ(), … nếu bên trong thân hàm xử lý những logic có độ phức tạp tính toán lớn, bởi những cái tên này thường dễ khiến người đọc hiểu lầm là hàm này "lightweight" nên dùng 1 cách thường xuyên.
    // Tên hàm gây hiểu lầm
    func getNumberOfSelectedObjects() -> Int {
       Loop hundreds time to calculate ...
    }
    • 1 vài kiểu gây hiểu lầm khác như đặt tên biến là objectIndexs nhưng lại trả về kiểu Object, …
    • Những cái tên trả về kiểu boolean thì tên biến, hàm nên bắt đầu bằng các từ như is, has, can, should, … để làm cho rõ nghĩa.
    • 1 số cái tên thông dụng khác như max, min cho giới hạn; first, last cho thứ tự,…

    Kết luận

    Hãy cố gắng chọn những cái tên truyền đạt đầy đủ ý nghĩa và dễ hiểu đối với mọi người.

    Tham khảo

    Sách "The Art of Readable Code" – O’Reilly
    Sách "Clean Code" – Uncle Bob