几种常见的程序命名规则

正确并形象地给函数、变量命名,不仅可以增加程序的可读性,也是程序员编程风格的一种反映。较好的命名习惯,可以有效的提高程序的可维护性。以下介绍几种常用的变量命名规则。

一、匈牙利命名法【Hungarian】:广泛应用于Microsoft Windows这类环境中。

这种命名技术是由一位能干的 Microsoft 程序员查尔斯·西蒙尼(Charles Simonyi) 提出的。匈牙利命名法通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域,类型等。这些符号可以多个同时使用,顺序是先m_(成员变 量),再指针,再简单数据类型,再其他。例如:m_lpszStr, 表示指向一个以0字符结尾的字符串的长指针成员变量。

匈牙利命名法关键是:标识符的名字以一个或者多个小写字母开头作为前缀;前缀之后的是首字母大写的一个单词或多个单词组合,该单词要指明变量的用途。

例如:bEnable, nLength, hWnd。

匈牙利命名法中常用的小写字母的前缀:

前缀 类型 描述
a Array 数组
b BOOL 布尔
by BYTE 无符号字符
c char 字符
cb Count of bytes 字节数
cr Color reference value 颜色值
cx,cy Count of x,y(short) 长度
dw DWORD 双字(无符号长整形)
f Flags 标志
fn Function 函数
g_ Global 全局的
h HANDLE 句柄
i Integer(int) 整数
l Long(long) 长整数
lp Long point 长指针
m_ Data member of a class 类的数据成员
n Short(short) 短整型
np Near point 短指针
p Point 指针
s String 字符串
sz Zero terminated string 以0结尾的字符串
tm Text metric 文本规则
u Unsigned int 无符号整数
ul Unsigned long(ULONG) 无符号长整数
w WORD 无符号短整数
x,y x,y coordinates(short) 坐标
v Void

有关项目的全局变量用g_开始,类成员变量用m_。

前缀 类型 例子
C CDocument, CPrintInfo
m_ 成员变量 m_pDoc, m_nCustomers
g_ 全局变量 g_Servers

二、驼峰命名法【camelCase】:近年来越来越流行。

驼峰命名法,正如它的名称所表示的那样,指的是混合使用大小写字母来构成标识符的名字。其中第一个单词首字母小写,余下的单词首字母大写。
例如:

printEmployeePaychecks();

函数名中每一个逻辑断点都有一个大写字母来标记。

三、帕斯卡(Pascal)命名法:与驼峰命名法类似。
只不过驼峰命名法是第一个单词首字母小写,而帕斯卡命名法则是第一个单词首字母大写。因此这种命名法也有人称之为“大驼峰命名法”。
例如:

DisplayInfo();

UserName

都是采用了帕斯卡命名法。
在C#中,以帕斯卡命名法和骆驼命名法居多。
事实上,很多程序设计者在实际命名时会将驼峰命名法和帕斯卡结合使用,例如变量名采用驼峰命名法,而函数采用帕斯卡命名法。

四、下划线命名法。

下划线法是随着C语言的出现流行起来的,在UNIX/LIUNX这样的环境,以及GNU代码中使用非常普遍。
4.1 函数的命名
函数名使用下划线分割小写字母的方式命名:
设备名_操作名();

操作名一般采用:谓语(此时设备名作为宾语或者标明操作所属的模块)或者谓语+宾语/表语(此时设备名作为主语或者标明操作所属的模块) 等形式,如:
tic_init();
adc_is_busy();
uart_tx_char();

中断函数的命名直接使用 设备名_isr() 的形式命名,如:
timer2_isr();

4.2 变量的命名
变量的命名也采用下划线分割小写字母的方式命名。命名应当准确,不引起歧义,且长度适中。如:
int length;
uint32 test_offset;

单字符的名字也是常用的,如i, j, k等,它们通常可用作函数内的局部变量。tmp常用做临时变量名。
局部静态变量,应加s_词冠(表示static),如:
static int s_lastw;

全局变量(尤其是供外部访问的全局变量),应加g_词冠(表示global),如:
void (* g_capture_hook)(void);

4.3 常量及宏的命名
采用下划线分割大写字母的方式命名,一般应以设备名作为前缀,
防止模块间命名的重复。如:
#define TIMER0_MODE_RELOAD                2
#define TIMER2_COUNT_RETRIEVE(val)        ((uint16)(65536 – (val)))

  当然,看作接口的宏可以按照函数的命名方法命名,例如:
#define timer2_clear()                      (TF2 = 0)
#define timer0_is_expired()                (TF0)
  据考察,没有一种命名规则可以让所有的程序员赞同,程序设计教科书一般都不指定命名规则。命名规则对软件产品而言并不是“成败悠关”的事,我们不要化太多精力试图发明世界上最好的命名规则,而应当制定一种令大多数项目成员满意的命名规则,并在项目中贯彻实施。

 

Swift中安全优雅的使用UserDefaults

纳尼? 如此简单的 UserDefaults 怎么去优雅的使用? 这么简单的还能玩出花来? 没毛病吧?

嗯, 没毛病!


Objective-C 中的 NSUserDefaults 我们并不陌生, 通常作为数据持久化的一种方式, 一般用来存储用户信息和基础配置信息. Swift 中使用 UserDefaults 来替代 NSUserDefaults, 两者的使用基本相同.

let defaults = UserDefaults.standard
defaults.set(123, forKey: "defaultKey")
defaults.integer(forKey: "defaultKey")

Objective-C中需要调用 synchronize 方法进行同步, 但是在Swift中已经废弃了该方法, 所以不需要手动去调用.
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.

上面的用法是最基本的用法, 也是我们平常开发中使用频率最高的用法, 但也是最危险的用法, 为什么呢?

  1. 在应用内部我们可以随意地覆盖和删除存储的值, 直接使用字符串来作为存储数据的 key 是非常危险的, 容易导致存数据时使用的 key 和取数据的时候使用的 key 不一致.
  2. UserDefaults.standard 是一个全局的单例, 如果需要存储账户信息(AccountInfo), 配置信息(SettingInfo), 此时按照最基本的使用方式, 简单的使用 key 来存取数据, 那么 key 值会随着存储的数据越来越多, 到时候不管是新接手的小伙伴还是我们自己都很难明白每个 key 值对应的意义. 也就是说我们不能根据方法调用的上下文明确知道我存取数据的具体含义, 代码的可读性和可维护性就不高.所以我们要利用 Swift 强大的灵活性来让我们使用 UserDefaults 存取数据的时候更加便捷和安全.

所以要想把 UserDefaults 玩出花来就得解决下面两个问题:

  • 一致性
  • 上下文

一致性

使用 UserDefaults 存取数据时使用的 key 值不同就会导致存在一致性问题. 原因就在于通常我们在存取数据的时候, 手动键入 key 或者复制粘贴 key 可能会出错, 输入的时候也很麻烦. 那我们的目的就比较明确了, 就是为了让存取的 key 一致, 即使改了其中一个另外一个也随之更改.

解决办法:

  • 常量保存
  • 分组存储

常量保存字符串

既然涉及到两个重复使用的字符串, 很容易就想到用常量保存字符串, 只有在初始化的时候设置 key 值, 存取的时候拿来用即可, 简单粗暴的方式.

let defaultStand = UserDefaults.standard
let defaultKey = "defaultKey"
defaultStand.set(123, forKey: defaultKey)
defaultStand.integer(forKey: defaultKey)

是不是感觉有点换汤不换药? 上面使用常量存储 key 值, 虽然能够保证存取的时候 key 值相同, 但是在设置 key 值的时候稍显麻烦.
最重要的一点就是如果需要存很多账户信息或者配置信息的时候, 按照这种方式都写在同一处地方就稍微欠妥, 比如下面这个场景, 在 app 启动后, 需要存储用户信息和登录信息, 用户信息里面包含: userName, avatar, password, gender等, 登录信息里包含: token, userId, timeStamp等等, 也就说需要存两类不同的信息, 那么此时这种方式就不合时宜了, 我们就会想办法把同类的信息归为一组, 进行分组存取.

分组存储

分组存储 key 可以把存储数据按不同类别区分开, 代码的可读性和可维护性大大提升. 我们可以采用类class, 结构体struct, 枚举enum来进行分组存储 key, 下面使用结构体来示例.

// 账户信息
struct AccountInfo {
    let userName = "userName"
    let avatar = "avatar"
    let password = "password"
    let gender = "gender"
    let age = "age"
    
}
// 登录信息
struct LoginInfo {
    let token = "token"
    let userId = "userId"
}
// 配置信息
struct SettingInfo {
    let font = "font"
    let backgroundImage = "backgroundImage"
}

存取数据:

let defaultStand = UserDefaults.standard
// 账户信息
defaultStand.set("Chilli Cheng", forKey: AccountInfo().avatar)
defaultStand.set(18, forKey: AccountInfo().age)
// 登录信息
defaultStand.set("achj167", forKey: LoginInfo().token)
// 配置信息
defaultStand.set(24, forKey: SettingInfo().font)
        
let userName = defaultStand.string(forKey: AccountInfo().avatar)
let age = defaultStand.integer(forKey: AccountInfo().age)
let token = defaultStand.string(forKey: LoginInfo().token)
let font = defaultStand.integer(forKey: SettingInfo().font)

上下文

上面这种方式是不是比直接使用常量的效果更好? 但是仍然有个问题, 账户信息, 登录信息, 配置信息都是属于要存储的信息, 那我们就可以把这三类信息归到一个大类里, 在这个大类中有这三个小类, 三个小类作为大类的属性, 既能解决一致性问题, 又能解决上下文的问题, 需要存储到 UserDefaults 里面的数据, 我只需要去特定的类中找到对应分组里面的属性即可. 示例:

struct UserDefaultKeys {
    // 账户信息
    struct AccountInfo {
        let userName = "userName"
        let avatar = "avatar"
        let password = "password"
        let gender = "gender"
        let age = "age"
    }
    // 登录信息
    struct LoginInfo {
        let token = "token"
        let userId = "userId"
    }
    // 配置信息
    struct SettingInfo {
        let font = "font"
        let backgroundImage = "backgroundImage"
    }
}

存取数据:

let defaultStand = UserDefaults.standard
// 账户信息
defaultStand.set("Chilli Cheng", forKey:UserDefaultKeys.AccountInfo().userName)
defaultStand.string(forKey: UserDefaultKeys.AccountInfo().userName)

上面的代码看起来可读性好了很多, 不仅是为了新接手的小伙伴能看懂, 更是为了我们自己过段时间能看懂. 我亲眼见过自己写的代码看不懂反而要进行重构的小伙伴.

避免初始化

但是上面的代码存在一个明显的缺陷, 每次存取值的时候需要初始化 struct 出一个实例, 再访问这个实例的属性获取 key 值, 其实是不必要的, 怎么才能做到不初始化实例就能访问属性呢? 可以使用静态变量, 直接通过类型名字访问属性的值.

struct AccountInfo {
    static let userName = "userName"
    static let avatar = "avatar"
    static let password = "password"
    static let gender = "gender"
    static let age = "age"
}

存取的时候:

defaultStand.set("Chilli Cheng", forKey: UserDefaultKeys.AccountInfo.userName)
defaultStand.string(forKey: UserDefaultKeys.AccountInfo.userName)

枚举分组存储

上面的方法虽然能基本满足要求, 但是仍然不完美, 我们依然需要手动去设置 key, 当 key 值很多的时候, 需要一个个的设置, 那有没有可以一劳永逸的办法呢? 不需要我们自己设置 key 的值, 让系统默认给我们设置好 key 的初始值, 我们直接拿 key 去进行存取数据. Swift这么好的语言当然可以实现, 即用枚举的方式, 枚举不仅可以分组设置 key, 还能默认设置 key 的原始值. 前提是我们需要遵守 String 协议, 不设置 rawValue 的时候, 系统会默认给我们的枚举 case 设置跟成员名字相同的原始值(rawValue), 我们就可以拿这个 rawValue 来作为存取数据的 key.

struct UserDefaultKeys {
    // 账户信息
    enum AccountInfo: String {
        case userName
        case age
    }
}

// 存账户信息
defaultStand.set("Chilli Cheng", forKey: UserDefaultKeys.AccountInfo.userName.rawValue)
defaultStand.set(18, forKey: UserDefaultKeys.AccountInfo.age.rawValue)

// 取存账户信息
defaultStand.string(forKey: UserDefaultKeys.AccountInfo.userName.rawValue)
defaultStand.integer(forKey: UserDefaultKeys.AccountInfo.age.rawValue)

吼吼, 是不是感觉很方便, Swift 太棒了!
上面基本就能达到我们的目的, 既解决了一致性问题, 又有上下文知道我存取数据使用的 key 的含义. 但是代码看起来很冗余, 我不就需要一个key 嘛, 干嘛非要链式调用那么多层呢? 还有就是为啥我非要写 rawValue 呢? 如果新来的小伙伴不知道 rawValue 是什么鬼肯定懵逼.

优化 key 值路径

虽然上面的代码能很好的达到目的, 但是写法和使用上还是欠妥, 我们仍需要继续改进, 上面的代码主要存在两个问题:

  • key 值路径太长
  • rawValue 没必要写

我们先分析一下为什么会出现这个两个问题:
key值的路径长是因为我们想分组存储 key, 让key具有上下文, 可读性更改,
rawValue 的作用是因为我们使用枚举来存储 key, 就不需要去手动设置 key 的初始值.

看起来简直是”鱼和熊掌不能兼得”, 有什么办法能解决”鱼和熊掌”的问题呢?
那就是”砍掉抓着鱼的熊掌”. 也就是说我们必须先解决一个问题(先让熊抓鱼), 再想法”砍熊掌”.

有了上面的一系列步骤, 解决第一个问题并不像刚开始一样使用简单的字符串, 而必须是使用枚举, 在这个前提下去”抓鱼”. 也就是我能不能直接传枚举成员值进去, 先利用枚举的 rawValue 解决第一个问题,例如这样使用:

defaultStand.set("Chilli Cheng", forKey: .userName)
defaultStand.string(forKey: .userName)

很明显能够实现, 只要给 userDefaults 扩展自定义方法即可, 在自定义方法中调用系统的方法进行存取, 为了使用方便我们扩展类方法.示例:

extension UserDefaults {
    enum AccountKeys: String {
        case userName
        case age
    }
    
    static func set(value: String, forKey key: AccountKeys) {
        let key = key.rawValue
        UserDefaults.standard.set(value, forKey: key)
    }

    static func string(forKey key: AccountKeys) -> String? {
        let key = key.rawValue
        return UserDefaults.standard.string(forKey: key)
    }
}

// 存取数据
UserDefaults.set(value: "chilli cheng", forKey: .userName)
UserDefaults.string(forKey: .userName)

前置上下文

能实现上面的目的之一, 但是没有上下文, 既然在 key 那里不能加, 换一个思路, 那就在前面加, 例如:

UserDefaults.AccountInfo.set(value: "chilli cheng", forKey: .userName)
UserDefaults.AccountInfo.string(forKey: .userName)

要实现上面的实现方式, 需要扩展 UserDefaults, 添加 AccountInfo 属性, 再调用 AccountInfo 的方法, key值由 AccountInfo 来提供, 因为AccountInfo 提供分组的 key, 由于是自定义的一个分组信息, 需要实现既定方法, 必然想到用协议呀, 毕竟 Swift 的协议很强大, Swift 就是面向协议编程的.
那我们先把自定义的方法抽取到协议中, 额, 但是协议不是只能提供方法声明, 不提供方法实现吗? 谁说的? 站出来我保证不打死他! Swift 中可以对协议 protocol 进行扩展, 提供协议方法的默认实现, 如果遵守协议的类/结构体/枚举实现了该方法, 就会覆盖掉默认的方法.
我们来试着实现一下, 先写一个协议, 提供默认的方法实现:

protocol UserDefaultsSettable {
    
}

extension UserDefaultsSettable {
    static func set(value: String, forKey key: AccountKeys) {
        let key = key.rawValue
        UserDefaults.standard.set(value, forKey: key)
    }
    static func string(forKey key: AccountKeys) -> String? {
        let key = key.rawValue
        return UserDefaults.standard.string(forKey: key)
    }
}

只要我的 AccountInfo 类/结构体/枚举遵守这个协议, 就能调用存取方法了, 但是, 现在问题来了, 也是至关重要的问题, AccountKeys 从哪儿来? 我们上面是把 AccountKeys 写在UserDefaults扩展里面的, 在协议里面如何知道这个变量是什么类型呢? 而且还使用到了 rawValue, 为了通用性, 那就需要在协议里关联类型, 而且传入的值能拿到 rawValue, 那么这个关联类型需要遵守 RawRepresentable 协议, 这个很关键!!!

protocol UserDefaultsSettable {
    associatedtype defaultKeys: RawRepresentable
}

extension UserDefaultsSettable where defaultKeys.RawValue==String {
    static func set(value: String?, forKey key: defaultKeys) {
        let aKey = key.rawValue
        UserDefaults.standard.set(value, forKey: aKey)
    }
    static func string(forKey key: defaultKeys) -> String? {
        let aKey = key.rawValue
        return UserDefaults.standard.string(forKey: aKey)
    }
}

必须在扩展中使用 where 子语句限制关联类型是字符串类型, 因为 UserDefaults 的 key 就是字符串类型.
where defaultKeys.RawValue==String

在 UserDefaults 的扩展中定义分组 key:

extension UserDefaults {
    // 账户信息
    struct AccountInfo: UserDefaultsSettable {
        enum defaultKeys: String {
            case userName
            case age
        }
    }
    
    // 登录信息
    struct LoginInfo: UserDefaultsSettable {
        enum defaultKeys: String {
            case token
            case userId
        }
    }
}

存取数据:

UserDefaults.AccountInfo.set(value: "chilli cheng", forKey: .userName)
UserDefaults.AccountInfo.string(forKey: .userName)
        
UserDefaults.LoginInfo.set(value: "ahdsjhad", forKey: .token)
UserDefaults.LoginInfo.string(forKey: .token)

打完收工, 既没有手动去写 key, 避免了写错的问题, 实现了key的一致性, 又实现了上下文, 能够直接明白 key 的含义.
如果还有需要存储的分类数据, 同样在 UserDefaults extension 中添加一个结构体, 遵守 UserDefaultsSettable 协议, 实现 defaultKeys 枚举属性, 在枚举中设置该分类存储数据所需要的 key.

注意: UserDefaultsSettable 协议中只实现了存取 string 类型的数据, 可以自行在 UserDefaultsSettable 协议中添加 Int, Bool等类型方法. 虽然这种用法前期比较费劲, 但是不失为一种管理 UserDefaults 的比较好的方式.
如果大家有更好的方式, 欢迎交流.