Category: Common Knowledge

  • Tips to ask a good question or make a better request

    Tips to ask a good question or make a better request

    Gần đây, mình thấy rất nhiều các bạn trẻ gặp phải các lỗi cơ bản về kỹ năng giao tiếp. Cụ thể là cách đặt câu hỏi/đưa ra yêu cầu trợ giúp. Dẫn đến việc có khá nhiều questions/requests không nhận được câu trả lời, hoặc câu trả lời không đúng như mong muốn của người hỏi.

    Để tránh rơi vào những tình huống như vậy, mình sẽ chỉ cho các bạn một số tips dựa trên kinh nghiệm làm việc với nhiều đối tác, khách hàng cũng như đồng nghiệp trong công ty của mình. Hy vọng sẽ giúp được các bạn phần nào.

    Đầu tiên, bạn cần nắm rõ vấn đề mà mình cần hỏi đã. Hãy tự trả lời những câu hỏi sau trước nhé:

    1. Context/Background (bối cảnh) là gì? Ví dụ:
      • Ai là người phát hiện (detect) ra?
      • Xảy ra ở giai đoạn (phase) nào của dự án?
    2. Problem/Issue (vấn đề) cụ thể là gì?
      • Liên quan đến tính năng (feature/module) nào trong requirements?
      • Tần xuất (frequency) xảy ra là bao nhiêu?
      • Đang hoặc có thể gây ra những ảnh hưởng (impact) gì?
      • Bạn đánh giá mức độ nghiêm trọng (severity) này ra sao (critical, high)?
    3. What have you done/Investigated Actions của bạn là gì rồi?
      • Bạn đã thực hiện tìm kiếm (search/research) thông tin trên Internet chưa?
      • Bạn đã thảo luận với các thành viên có kinh nghiệm hơn trong chính dự án (your team) chưa?
      • Bạn đã thử bao nhiêu phương án (try how many solutions) để giải quyết vấn đề rồi? Cụ thể là gì, kết quả (result) của từng phương án ra sao, đang bị tắc chỗ nào?

    Sau khi đã nắm khá rõ vấn đề và thử một số cách rồi nhưng vẫn chưa ra kết quả, thì mới đi tìm kiếm thêm sự giúp đỡ bạn nhé. Bây giờ, bạn cần xác định thêm:

    1. Right People, người/nhóm cụ thể bạn cần hỏi/tìm kiếm sự trợ giúp là ai? Đừng quăng vấn đề của mình vào 1 group quá lớn hoặc send email tới quá nhiều người mà mong có sự phản hồi nha.
    2. TODO List, những công việc cần làm tiếp theo là gì? Người bạn đang hỏi có thể giúp được gì trong đó?
      • TODO list để giải quyết vấn đề trên có thể là cả tá items, nên bạn cần xác định rõ những items nào bạn và team có thể xoay sở tiếp được, những items nào thực sự vượt quá năng lực/khả năng thì mới nhờ nhé.
    3. Bạn mong muốn nhận được phản hồi hay sự trợ giúp vào lúc nào? Đừng đưa ra một cái expected due time quá gấp, trong trường hợp vấn đề của bạn thực sự urgent thì hãy hỏi xem khi nào người trợ giúp bố trí được thời gian để bạn trình bày trực tiếp với họ nhé.

    Tóm lại, trước khi hỏi hay tìm kiếm sự giúp đỡ, hãy chắc chắn rằng bạn đã hiểu rõ (understand clearly) vấn đề của mình. Và bóc tách (break) vấn đề ra càng nhỏ càng tốt (as small as possible). Khi đó, bạn sẽ rất ngạc nhiên là sao vấn đề to tát của mình nó lại trở nên simple như thế và từ đó bạn có thể ask the Right People the Right Questions.

  • MVVM with swift P1

    MVVM with swift P1

    Bài viết này, mình sẽ nói về MVVM.
    Bài viết khá dài, nên sẽ được chia thành 2 phần.

    Content:

    Phần 1:

    • Mô hình MVVM?
    • Demo:
      • MVVM with Closure/Callback
      • Optimize MVVM with Closure/Callback

    Phần 2:

    • Demo:
      • MVVM with RxSwift
    • Tác dụng của MVVM
    • MVVM vs MVP?
    • Kết luận

    MVVM là gì?

    MVVM gồm 3 phần:

    Model:

    Tương tự Model Layer của MVC, MVP.

    View:

    Tương tự so với View Layer của MVP.

    ViewModel:

    • Đây là phần khác biệt của MVVM so với MVP.
    • ViewModel nằm giữa Model và View, có tác dụng là cầu nối để giao tiếp giữa Model và View.
    • ViewModel còn có tác dụng xử lí các logic convert, format data trước khi View hiển thị data đó cho người dùng.
    • Ngoài ra, ViewModel cũng có thể chứa các logic như update database, xử lí networking, … .Tuy nhiên, nên tách các logic này ra thành các class khác để đảm bảo nguyên tắc Single Responsibility Principle.

    Note:

    • View không tương tác trực tiếp với Model. View chỉ có thể tương tác với Model thông qua ViewModel.
    • ViewModel không biết gì về View.
    • MVVM giao tiếp giữa ViewModel và View bằng cách Observer (thường được gọi là binding data).
    • ViewModel cũng không được import UIKit để tách biệt phần logic ra khỏi phần UI.

    Demo MVVM with Closure/Callback:

    Model Layer

    import Foundation
    
    struct Category {
        let id: Int
        var name: String
    }
    

    ViewModel Layer:

    Giờ thì hãy tạo 1 ViewModel để thực hiện các logic xử lí data trước khi View hiển thị cho người dùng:

    import Foundation
    
    class HomeViewModel {
        private var category: Category
        
        // 1
        var displayName: String {
            return transformName(category.name)
        }
        
        // 2
        var onSuccess: ((String) -> Void)?
        
        init(category: Category) {
            self.category = category
        }
        
        // 3
        func transformName(_ name: String) -> String {
            let newName = name.enumerated().map { (index, character) -> String in
                if index % 2 == 0 {
                    return character.uppercased()
                } else {
                    return character.lowercased()
                }
            }
            
            return newName.joined()
        }
        
        // 4
        func changeCategoryName(_ newName: String) {
            category.name = newName
            onSuccess?(transformName(newName))
        }
    }
    
    1. Thuộc tính displayName có giá trị là tên của category sau khi đã được transform.
    2. Tạo 1 closure để phát event.
    3. func thực hiện logic transform name trước khi hiển thị cho người dùng.
    4. func thực hiện update name cho category, sau khi update thành công thì phát ra event chứa giá trị mới.

    View Layer

    import UIKit
    
    class HomeView: UIViewController {
        @IBOutlet private weak var categoryNameLabel: UILabel!
        
        // 1
        private var viewModel: HomeViewModel?
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // 2
            let category = Category(id: 1, name: "Hollywood")
            viewModel = HomeViewModel(category: category)
            categoryNameLabel.text = viewModel?.displayName
            
            // 3
            viewModel?.onSuccess = {
                self.categoryNameLabel.text = $0
            }
            
        }
    
        @IBAction func didTapButtonChangeCategory(_ sender: Any) {
            viewModel?.changeCategoryName("vietnam")
        }
    }
    1. Khai báo 1 viewModel cho View.
    2. Khởi tạo viewModel và hiển thị name sau khi transform lên giao diện để hiển thị cho người dùng.
    3. Thực hiện observer event được phát ra từ viewModel và update UI.

    Optimize Closure?

    Với cách sử dụng closure ở trên thì bạn đã tạo ra 1 binding giữa View và Model. Nhưng nếu có nhiều thuộc tính phải transform, thì chúng ta phải tạo ra nhiều closure ở ViewModel.
    Liệu có cách nào có thể optimize chúng không?

    Câu trả lời là sử dụng Generic. Tạo ra 1 class generic để tự động phát ra 1 event khi được set 1 value mới:

    class Binding<T> {
        typealias Listener = (T) -> Void
        var listener: Listener?
        
        var value: T {
            didSet {
                listener?(value)
            }
        }
        
        init(value: T) {
            self.value = value
        }
        
        func bind(listener: Listener?) {
            self.listener = listener
            self.listener?(value)
        }
    }

    ViewModel Layer khi đó sẽ được update lại như sau:

    class HomeViewModel {
        var category: Category
        // 1
        public var displayName: Binding<String>
        
        init(category: Category) {
            self.category = category
            
            let newName = category.name.enumerated().map { (index, character) -> String in
                if index % 2 == 0 {
                    return character.uppercased()
                } else {
                    return character.lowercased()
                }
            }
            self.displayName = Binding<String>(value: newName.joined())
        }
        
        func changeCategoryName(_ newName: String) {
            category.name = newName
            // 1
            displayName.value = transformName(newName)
        }
        
        func transformName(_ name: String) -> String {
            let newName = name.enumerated().map { (index, character) -> String in
                if index % 2 == 0 {
                    return character.uppercased()
                } else {
                    return character.lowercased()
                }
            }
            
            return newName.joined()
        }
    }
    1. displayName được sửa lại thành kiểu Binding<String>
    2. Mỗi khi thay đổi tên, chỉ cần set lại value cho displayName, thì displayName sẽ tự động phát ra 1 event chứa newName.

    View Layer sau khi optimize sẽ trở thành:

    class HomeView: UIViewController {
        @IBOutlet private weak var categoryNameLabel: UILabel!
        private var viewModel: HomeViewModel?
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let category = Category(id: 1, name: "Hollywood")
            viewModel = HomeViewModel(category: category)
            
            // 1
            viewModel?.displayName.bind(listener: {
                self.categoryNameLabel.text = $0
            })
            
        }
    
        @IBAction func didTapButtonChangeCategory(_ sender: Any) {
            viewModel?.changeCategoryName("vietnam")
        }
    }
    1. Thực hiện observer event được phát ra từ displayname của ViewModel và thực hiện update UI với giá trị mới nhận được.

    Note: Để tránh bài viết quá dài, thì mình sẽ không add ảnh kết quả run demo vào đây.

    Kết luận:

    Qua phần 1, có thể thấy 1 lợi ích của MVVM so với MVP là không cần tạo delegate để giao tiếp.
    Phần 1 sẽ chỉ dừng ở đây thôi, mình sẽ cùng tìm hiểu rõ hơn MVVM ở phần 2.

  • MVP with Swift

    MVP with Swift

    Bài viết này, mình sẽ trình bày về MVP.

    Content

    • Tổng quan về MVC
    • Những vấn đề của MVC
    • Ý tưởng của MVP
    • Áp dụng MVP

    Tổng quan về MVC:

    Chắc hẳn các bạn đã quen với MVC: 1 trong những architecture pattern phổ biến nhất.
    MVC bao gồm 3 phần chính:

    Model

    Model Layer là nơi lưu trữ data của bạn.
    Model layer bao gồm:

    • Model Objects (hiển nhiên rồi).
    • Ngoài ra còn có 1 vài class và object khác có thể trong Model như các class xử lí Network code, Persistence code(Lưu trữ data đến database, CoreData,…), Parsing Code(parsing network response to model,…), các class Helper, Extensions, …

    View

    Là nơi hiển thị giao diện cho người dùng.
    VD: Xib file, UIView class, Core Animation, … Nói chung là những thứ liênn quan đến UIKit

    Controller

    Là phần tương tác với View và Model. Controller nhận action/events từ view để update Model và View.

    Những điều cần chú í ở MVC:

    • View chỉ dùng để update UI, không chứa các logic.
    • View không được tương tác trực tiếp với Model.
    • View không được làm bất cứ điều gì mà không liên quan đến chính nó.

    Vấn đề của MVC?:

    • Controller chứa rất nhiều logic như update UI, xử lí các logic user action, logic networking, … tuy điều này có thể được giảm thiểu bởi tách các logic như Network, Lưu database,… sang các class khác.
    • Khó test các logic của controller bởi controller liên kết chặt chẽ với View.
    • Dự án càng lớn, code controller càng nhiều, dẫn đến khó maintain, bảo trì.

    Ý tưởng của MVP:

    MVP gồm 3 phần:

    View:

    Bao gồm Views và View Controller, đùng để tương tác, xử lí UI và nhận các event của user.

    Presenter:

    Chịu trách nhiệm xử lí logic, gồm các logic xử lí user action, logic networking, tương tác database…
    Các event của user sẽ được gửi đến Presenter để Presenter xử lí logic tương ứng, sau đó sẽ tương tác, yêu cầu View update UI bằng cách sử dụng delegate.

    Model:

    Tương tự như MVC.

    Note: Tầng Presenter phải không phụ thuộc vào UIKit, để tách biệt với View. Qua đó gíup dễ viết test cho phần logic hơn.

    Giờ hãy bắt tay vào demo:

    Demo:

    Model Entity:

    import Foundation
    
    struct Category {
        let id: Int
        let name: String
    }
    

    Class Service chịu trách nhiệm tương tác với Model:

    import Foundation
    
    protocol ICategoryService {
        func getCategoryById(id: Int) -> Category?
    }
    
    class CategoryService: ICategoryService {
        func getCategoryById(id: Int) -> Category? {
            let categories = [Category(id: 1, name: "Movies"),
                              Category(id: 2, name: "Books"),
                              Category(id: 3, name: "Computer")]
            
            let category = categories.filter({
                $0.id == id
            }).first
            
            return category
        }
    }

    Class Presenter: Chịu trách nhiệm xử lí logic

    import Foundation
    
    protocol IHomeViewDelegate: NSObjectProtocol {
        func updateUI(_ categoryName: String)
    }
    
    class HomeViewPresenter {
        // 1
        weak var delegate: IHomeViewDelegate?
        let categoryService: ICategoryService!
        
        init(view: IHomeViewDelegate, service: ICategoryService) {
            delegate = view
            categoryService = service
        }
        
        func searchCategoryById(id: Int) {
            let result = categoryService.getCategoryById(id: id)
            if let categoryName = result?.name {
                // 2
               delegate?.updateUI(categoryName)
            }
        }
    }
    1. Khai báo delegate để presenter có thể dùng để tương tác với View.
      Ở đây, delegate và category được khai báo kiểu protocol và được khởi tạo trong hàm init để tránh bị dependency.
    2. Sau khi thực hiện xong việc truy vấn database, presenter dùng delegate để yêu cầu View update UI.
    Class View:
    import UIKit
    
    class HomeView: UIViewController {
        @IBOutlet private weak var categoryNameLabel: UILabel!
        
        private var presenter: HomeViewPresenter!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            presenter = HomeViewPresenter(view: self, service: CategoryService())
        }
        
        // 1
        @IBAction func didTapButtonSearch(_ sender: Any) {
            presenter.searchCategoryById(id: 1)
        }
    }
    
    extension HomeView: IHomeViewDelegate {
        // 2
        func updateUI(_ categoryName: String) {
            categoryNameLabel.text = categoryName
        }
    }
    1. Khi user tap button, View sẽ chuyển action đến presenter và yêu cầu presenter thực hiện logic.
    2. Update UI khi được presenter yêu cầu.

    Lợi ích của MVP:

    MVP đã giúp tách biệt logic khỏi View, từ đó mang đến các lợi ích:

    • Dễ dàng đọc hiểu code.
    • Dễ dàng viết Unit test cho logic.
    • Dễ maintain, bảo trì.

    Kết luận:

    • MVP chỉ giải quyết việc tách biệt logic khỏi View. Tuy nhiên, những phần logic như truy vấn database, networking, … thì chưa được xử lí. Những logic như vậy có thể được đặt ở presenter, hoặc tạo 1 class riêng ở Model tùy theo bản thân bạn.
      Tuy nhiên, nên tách biệt các phần logic Database, networking, … ra các class riêng để tuân thủ nguyên tắc Single Responsibility Principle của SOLID.
    • Theo mô hình MVP, Presenter không được import UIKit để tránh logic bị liên quan đến View.
  • Dependency Injection trong iOS

    Dependency Injection trong iOS

    Trong lập trình OOP hay POP, thì luôn dc recommend code 1 cách tách biệt hệ thống thành những module sao cho chúng liên kết với nhau 1 cách lỏng lẻo, để những module đó có thể hoạt động 1 cách độc lập nhiều nhất có thể.
    Nếu các module liên kết quá chặt chẽ với nhau thì khi hệ thống cần nâng cấp, phát triển thì sẽ gặp nhiều vấn đề, ngoài ra việc maintain sau này hay việc viết unit test cũng gặp nhiều khó khăn.
    -> Dependency Injection (DI) là 1 design pattern để ngăn chặn sự phụ thuộc chặt chẽ này.

    Content

    • Ý tưởng của DI
    • Implement DI trong swift

    Ý tưởng của DI:

    Chữ D trong SOLID, là Dependency Inversion Principle. Nội dung của nguyên lý này được tóm gọn lại như sau:

    • Các module cấp cao không nên phụ thuộc vào các module cấp thấp.
    • Interface không nên phụ thuộc vào chi tiết, mà ngược lại.
    • Các class giao tiếp với nhau thông qua interface, chứ không phải implementation.

    Note:

    • Dependency Injection là 1 design pattern để code có thể tuân thủ nguyên lý Dependency Inversion.
    • DI là 1 kỹ thuật cho phép xóa bỏ sự phụ thuộc chặt chẽ giữa các module, làm cho ứng dụng dễ dàng hơn trong việc maintain, extend, debug và test bởi khi đó các module hoạt động độc lập.

    Implement DI trong swift:

    Trước hết hãy đến 1 ví dụ không sử dụng DI:

    Vì k sử dụng DI, ví dụ ở trên sẽ có những hạn chế như sau:
    Class Car bị phụ thuộc vào NormalEngine:

    • Trong trường hợp muốn nâng cấp bằng cách thay vào FastEngine, hay nhiều loại Engine khác thì sẽ gặp nhiều vấn đề,
    • Khi có thay đổi trong class NormalEngine, thì có thể sẽ ảnh hưởng trực tiếp đến class Car.
      Ex: Thêm nhiều thuộc tính cho NormalEngine thì constructor sẽ thay đổi.
    • Khó khăn trong việc viết unit test.

    -> Chúng ta sẽ áp dụng DI Design pattern để giải quyết điều này.

    Constructor Injection

    // 1
    protocol Engine {
        func start()
    }
    
    // 2
    class NormalEngine: Engine {
        func start() {
            print("Normal engine start running...")
        }
    }
    
    class FastEngine: Engine {
        func start() {
            print("Fast engine start running...")
        }
    }
    
    class Car {
        // 3
        var engine: Engine
        init(engine: Engine) {
            self.engine = engine
        }
    
        func run() {
            engine.start()
        }
    }
    
    // 4
    let car = Car(engine: NormalEngine())
    car.run()
    car.engine = FastEngine()
    car.run()
    1. Tạo 1 protocol(interface) để giao tiếp với class thay vì giao tiếp bằng implementation (nguyên lý Dependency Inversion).
    2. Các class NormalEngine, FastEngine implement protocol đó
    3. Khai báo 1 biến kiểu Engine trong class Car
    4. Khi khởi tạo class Car, chúng ta sẽ truyền 1 engine cụ thể vào thông qua constructor.
    Ưu điểmNhược điểm
    – Tính đóng gói cao
    – Chắc chắn rằng đối tượng đã được khởi tạo
    – Nếu giao tiếp với nhiều protocol thì sẽ làm cho constructor của class đó nhiều lên.
    – Đối với các ViewController, đặc biệt là ViewController được define trong storyboard thì sẽ không có hàm khởi tạo.

    Property Injection

    Property Injection được dùng để tránh nhược điểm của Constructor Injection,bằng cách không khởi tạo các dependencies trong constructor mà khởi tạo bằng cách set value cho property.

    class Car {
        var engine: Engine!
    
        func run() {
            engine.start()
        }
    }
    
    let car = Car()
    car.engine = NormalEngine()

    Ưu điểm:

    • Cho phép set dependencies vào thời điểm bạn muốn.

    Nhược điểm:

    • Tính đóng gói thấp.
    • Vì các dependency được khai báo theo kiểu !, nên nếu quên không set dependency trước khi sử dụng thì dễ gây ra crash; còn nếu khai báo dependency theo kiểu optional thì mỗi lần sử dụng phải unwrap.

    Interface Injection

    • Ở đây bạn tạo ra 1 hàm setter để set dependency.
    protocol HasEngine {
        func setEngine(dependency: Engine)
    }
    
    class Car: HasEngine {
        var engine: Engine!
    
        func setEngine(dependency: Engine) {
            self.engine = dependency
        }
    
        func run() {
            engine.start()
        }
    }
    
    let car = Car()
    car.setEngine(dependency: FastEngine())

    Method Injection

    • Nếu bạn chỉ cần sử dụng dependency để dùng 1 lần thì bạn không cần lưuu lại như là 1 biến. Bạn chỉ đơn giản là pass dependency như là 1 method param.
    class Car {
        func run(engine: Engine) {
            engine.start()
        }
    }
    let car = Car()
    car.run(engine: NormalEngine())

    Ps: Thật ra ví dụ này không phù hợp thực tế lắm bởi xe nào cũng cần động cơ :)) Nhưng để hiểu được thì thế là đủ hiểu r 😉

    Ambition Context:

    • Đây là 1 cách tránh DI nhưng chỉ nên được sử dụng cho các global dependency mà được chia sẻ với nhiều đối tượng khác.
    • Tuy nhiên bạn nên hạn chế sử dụng cách này, bởi nếu không quản lí đọc ghi 1 biến dùng chung 1 cách cẩn thận thì sẽ dễ dẫn tới Race Condition.
    class Car {
        static var engine: Engine = NormalEngine()
    
        func run() {
            Car.engine.start()
        }
    }
    
    let car = Car()
    car.run()

    Ưu điểm:

    • Dependency được sử dụng global.
    • Vẫn có thể thay đổi dependency trong quá trình sử dụng.

    Nhược điểm:

    • Tính đóng gói thấp.
    • Yêu cầu Thread safe.

    Kết luận:

    • Dependency Injection là 1 kỹ thuật mạnh mẽ để giúp viết code 1 cách clean và dễ maintain.
    • Ngoài ra có thể sử dụng các design pattern khác để tránh dependency như Factory, Service Locator hay Dependency Injection Container.
  • Phương pháp Bell Curve trong đánh giá Performance của nhân viên. Hay là: “tại sao em perform ko đến nỗi tệ mà vẫn bị đánh giá D” ?!

    Phương pháp Bell Curve trong đánh giá Performance của nhân viên. Hay là: “tại sao em perform ko đến nỗi tệ mà vẫn bị đánh giá D” ?!

    Hệ thống đánh giá Performance bằng phương pháp Bell Curve là hệ thống mà trong đó cấp quản lý bắt buộc phải đánh giá nhân viên của mình qua việc xếp hạng performance theo thứ tự từ cao xuống thấp. Thông qua hệ thống này, công ty cố gắng phân tách được những người có performance tốt nhất, bình thường, và tệ nhất, từ đó tập trung nuôi dưỡng, phát triển những người giỏi nhất và thay thế những người tệ nhất. Sự phân tách này dựa trên cơ sở so sánh tương đối về performance của những người làm các hoạt động tương đương nhau, rồi xếp hạng họ theo sự so sánh về performance đó.

    Phương pháp Bell Curve dựa trên các mô hình xác xuất thống kê. Nó giả định rằng năng lực của nhân viên trong 1 công ty sẽ được phân bố thành các nhóm với tỉ lệ như sau:

    • High Performers – Top 20%
    • Average Performers – 70% ở giữa
    • Non-Performers hay là Below Average Performers – 10% cuối.

    Vì thế, bằng việc chấm điểm performance (rating) cho từng nhân viên rồi ranking họ, sau đó lấy ra 20% đầu thì sẽ định danh được đó chính là “High Performers” đem lại phần lớn hiệu quả trong công ty. 70% tiếp theo sẽ là “Average Performers” còn 10% cuối chính là những Below Average Performers đang không đem lại hiệu quả tốt.
    (Cách gọi mỗi nhóm và tỉ lệ chia được đưa ra dựa trên phân tích của lãnh đạo từng công ty về trình độ nhân sự, đặc thù công việc, bản chất lĩnh vực hoạt động của công ty. Ví dụ như ở Fsoft quy định là: A+: top 5% performance cao nhất, A: từ 20% đến 30%, B: Từ 55% đến 65% ở giữa, C: 9%, D: 1% có performance thấp nhất)

    Phương pháp Bell Curve ép buộc cấp quản lý phải ranking nhân viên của mình như thế này cũng có những điểm có lợi và bất lợi riêng của nó.

    Lợi ích của hệ thống đánh giá Performance bằng Bell Curve

    1. Bắt Manager phải đối mặt trực diện với vấn đề và giải quyết kịp thời

    Các nhà quản lý có xu hướng đánh giá performance một cách nhẹ nhàng (thang điểm nhẹ tay) để không tạo ra bất kỳ căng thẳng nào, tránh các cuộc hội thoại thường không thoải mái có thể làm mất lòng nhân viên. Điều này có thể dẫn đến tình trạng “lạm phát điểm số” trong công ty. (Điểm số của tất cả các nhân viên đều cao nhưng không nói lên thực trạng của vấn đề).

    Phương pháp Bell Curve không để điều đó xảy ra. Nó buộc manager phải xếp hạng nhân viên của mình, từ đó bộc lộ ra những nhóm người được đánh giá là Below Average Performers cho dù điểm rating có cao hay thấp. Vì thế, phương pháp này buộc những người quản lý phải có những cuộc trò chuyện hứa hẹn là khó khăn với Below Average Performers, và (có lẽ là) các vấn đề nghiêm trọng sẽ được giải quyết ngay sau đó.

    2. Có thể là một công cụ tạo động lực tăng năng suất

    Phương pháp Bell Curve giúp chính công ty lẫn từng nhân viên có động lực để trưởng thành hơn nhờ vào áp lực cạnh tranh rất lớn mà nó đem đến. Nhớ rằng: trong phương pháp này, để được đánh giá là Top Performers, việc duy nhất một nhân viên có thể làm là thể hiện tốt hơn người khác.

    3. Nhanh chóng xác định được top performers

    Xác định được những người làm việc hiệu quả hàng đầu (top performers) và thưởng cho họ xứng đáng luôn là một việc hết sức quan trọng đối với 1 công ty. Việc này giúp công ty giữ chân được những người tài năng nhất. Bell Curve là phương pháp dễ dàng và nhanh chóng để giúp manager xác định được đâu thực sự là top performers trong công ty.

    Điểm bất lợi của Bell Curve: Quá cứng nhắc để phù hợp với tất cả – Great performers are still average

    Managers buộc phải xếp hạng đưa nhân viên và phân bố vào các nhóm trong Bell Curve, nghĩa là một số người được xếp loại là ‘bad’, hoặc ‘Below Average’ trong khi performance của họ không đến nỗi quá tệ. Họ có thể vẫn là những người thể hiện tuyệt vời và đáp ứng đúng kỳ vọng trong vai trò của họ, nhưng bị đẩy vào nhóm “Below Average Performers” đơn giản vì khi so sánh với đồng nghiệp của mình, họ không thể hiện vượt trội hơn được.
    Điều này có nghĩa là trong 1 nhóm gồm toàn người giỏi, thì người ít giỏi nhất sẽ vẫn bị xếp loại “Below Average Performers” trong khi nếu được xếp trong một môi trường khác, với cùng performance đó lại có thể được đánh giá là “xuất sắc”. Việc này có thể là yếu tố làm giảm tinh thần làm việc của nhân viên trong 1 số trường hợp.

    Kết mở:

    Không có mô hình nào là hoàn hảo. Quan trọng là cách vận dụng sao cho khéo léo và đúng bản chất, và vẫn có phương án backup trong một số trường hợp nhé 😛 .

    Tham khảo và lược dịch từ:

    http://www.cavinhr.com/relevance-of-bell-curve-method-of-performance-appraisal/

    https://blog.cake.hr/performance-appraisals-should-you-grade-your-employees-on-a-bell-curve-%F0%9F%94%94/

  • Video codec for Developer – Getting start

    Video codec for Developer – Getting start

    Chắc hẳn ace đều đã, đang và có thể sẽ làm việc liên quan tới xử lí video (video playing, video streaming, video processing, …). Đây là một thế giới với kho kiến thức khổng lồ, hàng trăm, hàng ngàn paper phải đọc. Mình quyết định sẽ viết một series liên quan tới video codec dưới góc độ làm việc của một developer chứ không phải là R&D để tránh lan man.

    Thông qua lượt bài này, mình hi vọng mọi người có thể tiếp cận nhanh và tối giản nhất có thể, để không cần phải đọc và hiểu hàng tá các thuật ngữ, các paper mà có thể khiến chúng ta nản ngay từ vòng gửi xe. Mình sẽ không đi quá sâu vào bất kì thành phần nào mà chỉ đưa ra những keyword cơ bản nhất, cũng như cố gắng giải thích dễ hiểu nhất nhằm giúp người đọc nắm bắt nhanh nhất.

    Video codec là gì?

    Nói một cách đơn giản thì đây là một (hoặc tập hợp) phương pháp mã hoá video. Mỗi video codec có thể được implement dựa vào phần cứng (hardware coding) hoặc phần mềm (software coding). Tất nhiên nếu sử dụng phần cứng thì sẽ luôn có sự tối ưu hơn với performance tốt hơn rất nhiều, nhưng một chipset sẽ không thể implement quá nhiều các loại thuật toán, vì thế nó vẫn luôn tồn tại song hành với software coding. Tuỳ vào cách lập trình của các lập trình viên cũng như sự hỗ trợ từ framework mà hardware coding có thể được ưu tiên hoặc không.

    Phần lớn các chuẩn mã hoá đều là lossy compression (nén có hư tổn dữ liệu) nhằm tối ưu dung lượng lưu trữ cũng như tối ưu truyền tải dữ liệu trên internet. Người nén có thể lựa chọn cân bằng chất lượng hình ảnh với dung lượng từ video gốc sao cho phù hợp với nhu cầu của họ cũng như người sử dụng.

    Có rất nhiều các loại codec cũng như các loại chuẩn (standard) được phát triển từ xưa tới nay, đến từ nhiều hãng khác nhau (như MPEG, Google, …), tuy nhiên mình sẽ chỉ focus vào nhóm chuẩn của MPEG (Moving Picture Experts Group) vì đây là các chuẩn thông dụng nhất, và cũng được hỗ trợ nhiều nhất ở thời điểm hiện tại.

    Sơ lược về cách hoạt động của quá trình nén

    Video codec làm gì mà thần thánh tới mức có thể giảm dung lượng video ghê gớm như vậy? Sau đây mình sẽ giải thích qua cơ chế xử lí của nó để các bạn nắm bắt được nhé.

    Một video gốc (raw video) bao gồm rất nhiều các frames (raw frame) nối tiếp với nhau. Mỗi raw frame sẽ chứa thông tin chính xác của từng điểm ảnh, vì thế dung lượng của video sẽ bằng dung lượng của frame nhân với số lượng frame. Thật là kinh hoàng nếu nghĩ tới 1 video 4K@60fps dài khoảng 1 tiếng đúng ko? Các đĩa Bluray ngoài rạp coi film phần lớn đều sử dụng các video gốc như vậy (thậm chí là nhiều lớp) để tăng độ chi tiết cũng như trải nghiệm của người xem ở rạp. Đó là lí do vì sao coi film chiếu rạp luôn có cảm giác "thật" hơn là coi film nén trên mạng, mặc dù độ phân giải là như nhau đó.

    Sẽ thật lãng phí nếu có một điểm ảnh không thay đổi trong suốt quá trình ghi lại video. Nếu điểm ảnh đó đã không thay đổi, thì chi bằng ta … khỏi lưu nó lại làm gì, phải không? Okay, đây là nguồn gốc quá trình nén của bất kì loại codec nào có hiện nay, chỉ có điều các kiểu nén có sự khác biệt về thuật toán mà thôi.

    +-------+     +-------+
    |   I   |     |   P   |
    +-------+     +-------+      +-------+
    |xxxxxxx|     | o     |      |xoxxxxx|
    |xxxxxxx|  +  |    o  |  ->  |xxxxoxx|
    |xxxxxxx|     |   o   |      |xxxoxxx|
    +-------+     +-------+      +-------+
    

    Ta tạm hiểu rằng mỗi video khi bắt đầu sẽ cần có 1 frame gốc để dựng hình, cái này gọi là intraframe (viết tắt là I-frame, hoặc còn được biết với các tên là keyframe). Đây là frame gốc, lưu trữ toàn bộ các điểm ảnh ban đầu để khi giải mã có thể dựng được frame đầu tiên (tất nhiên frame này cũng đã được nén, tương tự như nén ảnh). Sau intraframe là các interframe, mà nó không lưu trữ đủ thông tin các điểm ảnh, chỉ lưu trữ thông tin SỰ KHÁC NHAU giữa 2 frame. Có 2 loại interframe là P-frame (predictive frame) và B-frame (bi-directional predictive frame). P-frame được giải mã dựa trên I-frame hoặc P-frame ngay phía trước nó. B-frame cũng tương tự như P-frame, nhưng nó có thể giải mã dựa trên thông tin của cả frame phía sau nó nữa.

    Trong một video sẽ có rất nhiều intraframe, mà tần suất xuất hiện của nó được qui định bằng GOP (hoặc GOV). Mục đích của nó là để giải quyết bài toán seeking, cũng như hạn chế việc loss thông tin trong quá trình giải mã video thì cần sync lại để tránh video bị lỗi quá nhiều. GOP (GOV) là viết tắt của cụm từ Group of Picture (hoặc Group of Video), mô tả số lượng I/P/B frames từ keyframe này tới keyframe tiếp theo. Trong một số hệ thống encoding sẽ không xuất hiện tham số GOP này mà sử dụng tham số I-frame interval. Con số này mô tả rằng sau bao nhiêu giây sẽ xuất hiện 1 I-frame

    +-------------------------------------------------------------------------------+
    | I | P | P | P | P | P | P | P | P | P | I | P | P | P | P | P | P | P | P | P |
    +-------------------------------------------------------------------------------+
    |                GOP = 10               |                GOP = 10               |
    +-------------------------------------------------------------------------------+
    |                                   20 frames                                   |
    +-------------------------------------------------------------------------------+
    

    Nếu ta cho rằng video trong sơ đồ phía trên có framerate (tốc độ khung hình) là 20fps, vậy đồng nghĩa với việc I-frame interval là 0.5. Quá đơn giản phải không nào 😀

    Chất lượng của việc nén sẽ được qui định bằng con số bitrate, với đơn vị thường thấy là kbps (kilobits per sec). Hiểu một cách nôm na thì đây chính là kích thước thực của video trong mỗi giây. Con số này có thể là không đổi cho toàn bộ video (CBR – Constant Bitrate), hoặc cũng có thể thay đổi tuỳ vào thời điểm của video (VBR – Variable Bitrate), được qui định tại thời điểm encode. Giả dụ ta có 1 bài toán đơn giản như sau:

    1 video được nén CBR, độ dài là 30s với bitrate = 5120kbps
    =&gt; dung lượng của video = 5120 / 8 * 30 = 19200 KB ~ 18.75MB
    

    Như các bạn có thể thấy, dung lượng của video không còn quan tâm tới resolution (độ phân giải), mà dung lượng được quyết định bởi bitrate. Ồ, vậy thì độ phân giải còn có vai trò gì nữa? Tất nhiên là nó vẫn có rỗi. Với kiến thức thông thường, ta biết rằng độ phân giải sẽ quyết định mức độ chi tiết của ảnh. Độ chi tiết càng cao thì lượng thông tin càng lớn. Vì vậy nếu ta giữ nguyên bitrate để nén video với lượng thông tin lớn, thì hiển nhiên chất lượng của nó sẽ kém đi. Đây chính là lí do vì sao đôi khi ta gặp những video 4K nhưng lại có chất lượng kém hơn cả video 2K hay 1080p, đơn giản vì nó có bitrate thấp, không tương xứng với chất lượng hình ảnh.

    Như ví dụ dưới đây, mình sẽ minh hoạ 2 trường hợp phổ biến mà ta thường gặp. Cho rằng dữ liệu gốc là không đổi (4K lossless downscale xuống 1K lossless), các bạn thử tự hình dung xem kết quả nào cho chất lượng tốt hơn nhé?

    * TH1: nén video 4K với 300 units = bitrate 5120kbps?
    * TH2: nén video 1K với 100 units = bitrate 5120kbps?
    

    Trên đây mình đã mô tả một cách cơ bản nhất về quá trình mã hoá video. Các kiến thức trên là cơ bản nhất và nó gần như đúng với mọi loại thuật toán nén, mọi loại chuẩn nén. Các thuật toán đời mới hiện nay chỉ có sự khác biệt về tối ưu hoá chất lượng, dung lượng cũng như performance mà thôi.

    Hẹn gặp lại các bạn ở bài viết sau. Nếu có phần nào không đúng, nhờ các bạn góp ý để chúng ta có nhiều hiểu biết hơn nhé.