Coding convention – Những điều cần biết trước khi bắt tay vào code (Part 1)

by cuongpt
534 views

Table of contents

  • Đặt tên biến
  • Đặt tên hàm
  • Đặt tên class, struct, enum, protocol
  • Spacing
  • Comment
  • Access Control
  • Self & Closure

Đặt tên biến

  • Hai quy tắc cơ bản nhất khi đặt tên biến đó là: sử dụng tiếng Anh thay vì tiếng Việt, sử dụng lowerCamelCase (kiểu lạc đà) thay vì snake_case

Not Preferred

 private let height_normal_avatar: CGFloat = 60.0
 private let chieuRongNormalAvatar: CGFloat = 120.0

Preferred

 private let heightNormalAvatar: CGFloat = 60.0
 private let widthNormalAvatar: CGFloat = 120.0
  • Khi đặt tên biến, hãy chú trọng đến sự rõ ràng, rành mạch hơn là sự ngắn gọn. Cố gắng làm sao khi đọc tên biến lên, ta có thể tưởng tượng được ngay biến đó có nhiệm vụ gì hoặc đang ám chỉ đến đối tượng nào. Vì vậy khi đặt tên biến không nên viết tắt và cũng không nên đặt tên giống với các đối tượng của hệ thống

Not Preferred

@IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var imgAvatar: UIImageView!
@IBOutlet private weak var lblbName: UILabel!

private var bool: Bool = false

Preferred

@IBOutlet private weak var salaryTableView: UITableView!
@IBOutlet private weak var avatarImageView: UIImageView!
@IBOutlet private weak var nameLabel: UILabel!

private var isLoadingState: Bool = false
  • Tên biến nên được bắt đầu bằng 1 danh từ và khi khai báo biến, nên khai báo luôn kiểu dữ liệu của biến đó (điều này có thể làm giảm được phần nào thời gian compile của app)

Not Preferred

private var dataSalaryArray = [Salary]()
private var isLoadingState = false

Preferred

private var dataSalaryArray: [Salary] = [Salary]()
private var isLoadingState: Bool = false
  • Với những biến cùng kiểu, nên đặt tên có sự thống nhất từ trên xuống dưới, tránh tình trạng mỗi biến 1 style đặt tên khác nhau. Ví dụ trong trường hợp với height và top constraint của đối tượng avatarImageView

Not Preferred

@IBOutlet private weak var heightOfAvatarImageView: NSLayoutConstraint!
@IBOutlet private weak var topConstraintForAvatarImageView: NSLayoutConstraint!

Preferred

@IBOutlet private weak var heightConstraintAvatarImageView: NSLayoutConstraint!
@IBOutlet private weak var topConstraintAvatarImageView: NSLayoutConstraint!

Đặt tên hàm

  • Cũng giống như việc đặt tên biến, việc đặt tên hàm cũng có các quy tắc tương tự: dùng tiếng Anh, dùng kiểu lowerCamelCase (kiểu lạc đà)
  • Tên hàm thường được bắt đầu bằng động từ, tên hàm phải rõ ràng, rành mạch. Cố gắng làm sao khi đọc tên hàm lên, ta có thể tưởng tượng được ngay hàm đó làm nhiệm vụ gì.
  • Đối với những function có nhiều param, nên đặt mỗi param trên 1 dòng và căn lề cho chúng. Ngoài ra, với những param có giá trị mặc định, nên đặt chúng ở cuối list parameter. Còn những param không có giá trị mặc định thì nên đặt lên đầu

Not Preferred

func setAttributedString(string: String, font: UIFont, lineSpacing: CGFloat, alignment: NSTextAlignment = .left, icon: UIImage? = nil, íconRect: CGRect? = nil) -> NSAttributedString {
    // Do something
}

Preferred

func setAttributedString(string: String, 
                        font: UIFont, 
                        lineSpacing: CGFloat, 
                        lignment: NSTextAlignment = .left, 
                        icon: UIImage? = nil, 
                        íconRect: CGRect? = nil) -> NSAttributedString {
    // Do something
}
  • Bên cạnh việc sử dụng parameter 1 cách truyền thống, ta có thể sử dụng thêm specifying argument labels (thêm 1 label vào trước tên của param) hoặc omitting argument labels (thêm dấu gạch dưới _ vào trước tên của param). Điều này làm cho việc gọi tên hàm sẽ trở nên gần gũi hơn với ngôn ngữ tự nhiên. (Tham khảo thêm tại Function)

Preferred

func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)!  Glad you could visit from \(hometown)."
}

print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill!  Glad you could visit from Cupertino."

Preferred

func welcome(_ person: String, from hometown: String) -> String {
    return "Hello \(person)!  Glad you could visit from \(hometown)."
}

print(welcome("Bill", from: "Cupertino"))
// Prints "Hello Bill!  Glad you could visit from Cupertino."

Đặt tên class, struct, enum, protocol

  • Đặt tên class, struct, enum và protocol ta cũng sử dụng tiếng Anh nhưng sẽ dùng kiểu UpperCamelCase – đây là điểm khác biệt so với function và property
  • Tên class, struct, enum, protocol thường được bắt đầu bằng danh từ và khi đặt tên cũng cần ưu tiên sự rõ ràng, rành mạch.

Spacing

  • Các dấu ngoặc nhọn mở đầu cho các function và các dấu ngoặc sau các biểu thức if/else/switch/while… đều phải được mở trên cùng 1 dòng với câu lệnh, có thêm 1 khoảng trắng phía bên trái và đóng trên 1 dòng khác

Not Preferred

if user.isHappy
{
  // Do something
}
else {
  // Do something else
}

Preferred

if user.isHappy {
  // Do something
} else {
  // Do something else
}
  • Nên có 1 dòng trắng giữa các function, giữa các block code và giữa khu vực khai báo các properties với khu vực ánh xạ các outlet. Trong 1 function cũng cần có những dòng trắng để phân tách các chức năng nhỏ trong function đó.

Not Preferred

import UIKit
class SettingScreen: UIViewController {
    @IBOutlet private weak var settingTitleLabel: UILabel!
    @IBOutlet private weak var stateSettingSwitch: UISwitch!
    var snapView: UIView?
    var snapTabbarView: UIView?
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
}
extension SettingScreen {
    func loadData(state: String) {
        DispatchQueue.main.async {
            self.settingTitleLabel.text = state
        }
    }
}

Preferred

import UIKit

class SettingScreen: UIViewController {

    @IBOutlet private weak var settingTitleLabel: UILabel!
    @IBOutlet private weak var stateSettingSwitch: UISwitch!

    var snapView: UIView?
    var snapTabbarView: UIView?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
}

extension SettingScreen {

    func loadData(state: String) {
        DispatchQueue.main.async {
            self.settingTitleLabel.text = state
        }
    }
}
  • Dấu 2 chấm luôn không có khoảng trắng ở phía bên trái và có 1 khoảng trắng ở phía bên phải. Ngoại trừ 3 trường hợp: toán tử 3 ngôi A ? B : C, empty dictionary [:] và #selector syntax addTarget(_:action:)

Not Preferred

 class ViewController : UIViewController {
     private var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
 }

Preferred

 class ViewController: UIViewController {
     private var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
 }
  • Không nên có khoảng trắng ở cuối mỗi dòng code nhưng nên có thêm 1 dòng trắng ở cuối mỗi file
  • Không có giới hạn nhất định cho số ký tự trên mỗi dòng code. Tuy nhiên mỗi dòng code của bạn không nên có quá 100 ký tự. Có 1 vài cách để làm giảm số lượng ký tự trên 1 dòng code:
    • Đối với function có nhiều param và các param có tên quá dài, bạn có thể xuống dòng và căn lề cho chúng (đã để cập ở trên)
    • Đối với các biểu thức tính toán, bạn có thể đặt ra các biến phụ thay vì gộp chung lại vào 1 biểu thức

Not Preferred

// Tính diện tích tam giác bất kỳ khi biết độ dài 3 cạnh (hệ thức Heron)
func calculateSquareTriangleUsingHeron(firstEdge: CGFloat,
                                       secondEdge: CGFloat,
                                       thirdEdge: CGFloat) -> CGFloat {
    return sqrt(((firstEdge + secondEdge + thirdEdge) / 2) * ((firstEdge + secondEdge + thirdEdge) / 2 - firstEdge) * ((firstEdge + secondEdge + thirdEdge) / 2 - secondEdge) * ((firstEdge + secondEdge + thirdEdge) / 2 - thirdEdge))
}

Preferred

// Tính diện tích tam giác bất kỳ khi biết độ dài 3 cạnh (hệ thức Heron)
func calculateSquareTriangleUsingHeron(firstEdge: CGFloat,
                                       secondEdge: CGFloat,
                                       thirdEdge: CGFloat) -> CGFloat {
    let halfPerimeter: CGFloat = (firstEdge + secondEdge + thirdEdge) / 2
    let halfPerimeterMinusA: CGFloat = halfPerimeter - firstEdge
    let halfPerimeterMinusB: CGFloat = halfPerimeter - secondEdge
    let halfPerimeterMinusC: CGFloat = halfPerimeter - thirdEdge
    let doubleSquare: CGFloat = halfPerimeter * halfPerimeterMinusA * halfPerimeterMinusB * halfPerimeterMinusC
    let square: CGFloat = sqrt(doubleSquare)
    return square
}

Comment

  • Đôi khi ta cần phải add comment để chú thích cho các đoạn code, phục vụ cho quá trình maintenance sau này. Tất nhiên là khi code được thay đổi thì comment cũng cần được update theo.
  • Khi comment, không nên dùng C-style /*…*/ mà nên dùng double-slash // hoặc triple-slash ///. Cũng không nên để code và comment xuất hiện trên cùng 1 dòng

Access Control

  • Các function và property nên mặc định để là private hoặc fileprivate để đảm bảo tính đóng gói trong lập trình. Nên hạn chế việc sử dụng open, public hoặc internal. Tham khảo thêm tại Access Control

Not Preferred

class SalaryCell: UITableViewCell {
    @IBOutlet weak var monthLabel: UILabel!
    @IBOutlet weak var incomeLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSalaryArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = salaryTableView.dequeueReusableCell(withIdentifier: "SalaryCell",
                                                            for: indexPath) as? SalaryCell else {
            return UITableViewCell()
        }
        cell.monthLabel.text = dataSalaryArray[indexPath.row].month
        cell.incomeLabel.text = dataSalaryArray[indexPath.row].incomeLabel
        return cell
    }
}

Preferred

class SalaryCell: UITableViewCell {
    @IBOutlet private weak var monthLabel: UILabel!
    @IBOutlet private weak var incomeLabel: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    func setupData(data: Salary) {
        monthLabel.text = data.month
        incomeLabel.text = "\(data.income)"
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSalaryArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = salaryTableView.dequeueReusableCell(withIdentifier: "SalaryCell",
                                                            for: indexPath) as? SalaryCell else {
            return UITableViewCell()
        }
        cell.setupData(data: dataSalaryArray[indexPath.row])
        return cell
    }
}
  • Khi khai báo property, các từ khoá liên quan đến access control nên được đặt lên đầu. Chỉ có 1 số từ khoá được đứng trước chúng đó là: static, @IBAction, @IBOutlet, @discardableResult.

Not Preferred

@IBOutlet weak private var salaryTableView: UITableView!
@IBOutlet weak private var fullNameLabel: UILabel!

Preferred

@IBOutlet private weak var salaryTableView: UITableView!
@IBOutlet private weak var fullNameLabel: UILabel!

Self & Closure

  • Không nên sử dụng từ khoá self một cách tuỳ ý. Chỉ dùng self trong 2 trường hợp sau:
    • Khi trình biên dịch yêu cầu, thường là khi đang trong biểu thức closure
    func loadData() {
        DispatchQueue.main.async {
            self.salaryTableView.reloadData()
        }
    }
    
    • Khi đang ở trong hàm init, ta cần phân biệt giữa property của object và param của hàm init
    class Salary {
        var income: Int
        var month: String
    
        init(income: Int, month: String) {
            self.income = income
            self.month = month
        }
    }
    
  • Đối với closure, ta có thể dùng trailling closure syntax trong trường hợp chỉ có duy nhất 1 biều thức closure trong list parameter. Còn nếu có nhiều hơn 1, ta phải giữ lại tên cho các closure đó.

Not Preferred

// Trong trường hợp này, chỉ có duy nhất 1 biểu thức closure nên không cần thiết phải để lại label "animations"
UIView.animate(withDuration: 1, animations: {
    self.avatarImageView.alpha = 0.0
})
        
// Trường hợp này có 2 biểu thức closure, vì vậy nên để lại cả 2 label "animations" và "completion" để phân biệt chúng với nhau
UIView.animate(withDuration: 1) {
    self.avatarImageView.alpha = 0.0
} completion: { (_) in
    self.avatarImageView.removeFromSuperview()
}

Preferred

UIView.animate(withDuration: 1) {
    self.avatarImageView.alpha = 0.0
}
        
UIView.animate(withDuration: 1,
                animations: {
                    self.avatarImageView.alpha = 0.0
                }, completion: { _ in
                    self.avatarImageView.removeFromSuperview()
                })

To be continue…

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like