CodeComplete 笔记 第3部分 变量

266 阅读16分钟

10 General Lssues in Using Variables 使用变量的一般事项

10 General Lssues in Using Variables 使用变量的一般事项.png

Making Variable Declarations Easy

轻松掌握变量定义

Implicit Declarations

隐式声明

  • 关闭隐式声明
  • 声明全部的变量
  • 遵循某种命名规则
  • 检查变量名

Guidelines for Initializing Variables

变量初始化原则

不合理地初始化数据是产生编程错误常见的根源之一

  • 从未对变量赋值

  • 变量值已过期

    • 变量在某个地方曾经被赋值,但该值已经不再生效
  • 变量的一部分被赋值,另一部分没有

避免产生初始化错误的建议

  • 在声明变量时初始化

  • 在靠近变量第一次使用的位置初始化它

    • VB:糟糕的初始化
accountIndex = 0;
total=0.0;
done = False

code using accountIndex

code using total

code using done
	- VB:良好的初始化
accountIndex = 0;
code using accountIndex;

total=0.0;
code using total;

done = False;
code using done;
- 第二个示例在多个方面优于第一个。在第一个示例中,当使用done的代码开始执行时,done很可能已被修改了。

一旦把所有初始化代码都放在一起,也可能会让人产生误解,认为所有这些变量都会在子程序中一直使用 —— 而实际上done只是在后面才被用到。 最后,随着对程序的不断修改,一些循环可能会包含使用了done的代码,因此 done就需要重新初始化。

  • 理想情况下,在靠近第一次使用变量的位置声明和定义变量
  • 在可能的情况下使用final 或 const
  • 特别注意计数器和累加器
  • 在类的构造函数里初始化类的数据成员
  • 检查是否需要重新初始化
  • 一次性初始化具名常量; 用可执行代码来初始化变量
  • 使用编译器设置来自动初始化所有变量
  • 利用编译器的警告信息
  • 检查输入参数的合法性
  • 使用内存访问检查工具来检查错误的指针
  • 在程序刊时初始化工作内存

Scope

作用域

Keep Variables "Live" for as Short a Time as Possible

尽可能缩短变量的“存活”时间

  • 好处

    • 在你真正想要修改一个变量的那些位置之间的区域,该变量被错误或无意修改的可能性就降低了
    • 使你能对自己的代码有更准确的认识
    • 减少了初始化错误的可能
    • 提高程序的可读性
    • 当需要把一个大的子程序拆分成多个小的子程序时,短的变量存活时间也很有价值

General Guidelines for Minimizing Scope

减小作用域的一般原则

  • 在循环开始之前再去初始化该循环里使用的变量,而不是在该循环所属的子程序的开始处初始化这些变量
  • 直到变量即将被使用时再为其赋值
  • 把相关语句放到一起
  • 把相关语句组提取成单独的子程序
  • 开始时采用最严格的可见性,然后根据需要扩展变量的作用域

Persistence

持续性

持续性的多种形态

  • 特定代码段或子程序的生命期

    • 如for循环里声明的变量
  • 只要你允许,它就会持续下去

    • Java里,用new创建的变量会持续到它成为垃圾被回收为止
  • 程序的生命期

    • 大多数语言的全局变量
  • 永远持续

    • 如数据库中的数据

与持久性相关的最主要问题是变量实际生命期比你想象的要短,为避免此种问题,可采取以下措施

  • 在程序中加入调试代码或断言来检查那些关键变量的合理取值

  • 准备抛弃变量时轨它们赋上“不合理的数值”

    • 如 null
  • 编写代码时要假设数据没有持续性

    • 如某个变量在你退出子程序时具有特定的值,那当你下次进入该子程序时就不要假定该变量还有同样的值
  • 养成在使用所有数据之前声明和初始化的习惯

Binding Time

绑定时间

把变量和它的值绑定在一起的时间,越晚越有利,代码越灵活

  • 硬编码 titleBar.color = 0xFF

  • 编译时绑定 private static final int COLOR_BLUE = 0xFF; private static final int TITLE_BAT_COLIR = COLOR_BLUE; ... titleBar.color = TITLE_BAR_COLOR;

    • 总要好于硬编码
  • 运行时绑定 titleBar.color = ReadTitleBarColor();

    • 更具可读性和灵活性

Using Each Variable for Exactly One Purpose

为变量指定单一用途

每个变量只用于单一用途

避免让代码具有隐含含义

确保使用了所有已声明的变量

11 The Power of Variable Names 变量名的力量

11  The Power of Variable Names 变量名的力量.png

Considerations in Choosing Good Mames

选择好变量名的注意事项

The Most Important Naming Consideration

最重要的命名注意事项

  • 名字要完全、准确地描述出该变量所代表的事物
  • currentDate 和 todaysDate都是好名字

Problem Orientation

以问题为导向

  • 一个好记的名字反映的通常是问题,而不是解决方案。一个好名字通常表达的是“什么 what”,而不是“如何 how”

  • 一条员工数据记录可称作 inputRec或 employeeData

    • inputRec是一个反映输入、记录这些计算概念的计算机术语
    • employeeData则直指问题领域,与计算的世界无关

Optimum Name Length

最适当的名字长度

  • 推荐平均长度在10~16个字符

The Effect of Scope on Variable Names

作用域对变量名的影响

  • 当你把变量命名为 i 时,你就是在表示,“这是个普通的循环计数器或数组下标,在这几行代码之外它没任何作用”

Computed-Value Qualifiers in Variable Names

变量名中的计算值限定词

  • 如果要用类似Total / Sum / Max / Min 这样的限定词来修改某个名字,请记住把限定词加到名字最后。 一致性可提供可读性,简化维护工作

    • customerCount

      • 员工总数
    • customerIndex

      • 某个特定的员工

Common Opposites in Variable Names

变量名中的常用对仗词

  • begin / end first / last locked / unlocked min / max next / previous old / new opened / closed visible / invisible source / target source / destination up / down

Naming Specific Types of Data

为特定类型的数据命名

Naming Loop Indexes

为循环下标命名

  • 如果一个变量要在循环之外使用,那就应该取一个比 i j k 更有意义的名字

    • 如果你在从文件中读取记录,需要记下所读取记录的数量,那么 recordCount就很合适
  • 如果循环不是只有几行 或 使用了多个嵌套循环

    • score[teamIndex][eventIndex] 要比 score[i][j]给出的信息更多

Naming Status Variables

为状态变量命名

  • 为状态变量取一个比flag更好的名字

    • characterType = CONTROL_CHARACTER 要比 statusFlag = 0x80 更有意义
    • if(reportType == ReportType_Annual) 要比 if(printFlag == 16) 更为清晰

Naming Temporary Variables

为临时变量命名

  • 用真正的变量替代“临时”变量

Naming Boolean Variables

为不布尔变量命名

  • 典型的布尔变量名

    • done

      • 某件事已经完成
    • error

    • found

      • 某个值已找到
    • success 或 ok

      • 某项操作是否成功
  • 为了取得更好的效果

    • status

      • error 或 statusOK
    • sourceFile

      • sourceFileAvailable / sourceFileFound
  • 使用肯定的布尔变量名

    • 避免 notFound / notdone

Naming Enumerated Types

为枚举类型命名

  • 可通过使用组前缀 如 Color_ / Planet_ / Month_

Naming Constants

为常量命名

  • CYCLES_NEEDED / DONUTS_MAX

非正式命名规则

Guidelines for a Language-Independent Convention

与语言无关的命名规则的指导原则

  • 区分变量名与子程序名字

    • 变量名和对象名以小写开始
    • 子程序名以大写字母开始
  • 区分类和对象

    • Widget widget
  • 标识全局变量

    • 前缀 g_
  • 标识成员变量

    • 明确表示该变量即非全局变量,也非局部变量
    • 前缀 m_
  • 标识类型声明

    • 两个好处

      • 能明确表明一个名字是类型名
      • 避免类型名与变量名冲突
    • 前缀 t_

      • t_Color / t_Menu
  • 标识具名常量

    • 大写 RECS_MAX
  • 标识枚举类型的元素

    • 全部用大写,或前类型名增加e_或E_前缀
    • 同时为该类型的成员名增加基于特定类型的前缀,如 Color_ 或 Planet_

Standardized Prefixes

标准前缀

标准化的前缀由两部分组成

  • 用户自定义类型 UDT
  • 语义前缀

User-Defined Type Abbreviations

用户自定义类型缩写

  • UDT缩写可被用于表示像窗体、屏幕区域以及字体一类的实体,通常不会表示任何由编程语言所提供的预置数据类型

  • UDT缩写

    • ch

      • 字符(character 这里不是编程语言中的字符,而是指文字处理程序可能用于表示一份文档中的字符的数据类型)
      • chCursorPosition
    • doc

      • 文档 Document
      • docActive
    • pa

      • 段落 Paragraph
      • firstPaActiveDocument
    • scr

      • 屏幕区域 Screen region
      • scrUserWorkspace
    • sel

      • 选中范围 Selection
    • wn

      • 窗体 window

Semantic Prefixes

语义前缀

  • 比UDT更进一步,描述了变量或对象是如何使用的。语义前缀与UDT不同,后者根据项目的不同而不同,前者在某种程度上对不同的项目均是标准的

  • 语义前缀缩写

    • c

      • 数量 count
    • first

      • 数组中需处理的第一个元素,它是相对于当前操作而不是数组本身
    • g

      • 全局变量 global variable
    • i

      • 数组下标 index into an array
    • last

      • 数组中需要处理的最后一个元素,与first对应
    • lim

      • 数组需要处理元素的上限,lim表示的是数组中并不存在的上界; 而last表示的是最终的、合法的元素,通常 lim = last + 1
    • m

      • 类一级的变量
    • max

      • 数组或其他列表中绝对的最后一个元素 max反映的是数组本身,而不是针对数组的操作
    • min

      • 数组或其他列表中绝对的第一个元素
    • p

      • 指针 pointer

Advantages of Standardized Prefixes

标准前缀的优点

  • 使名字变得更加紧凑

    • cPa 而不是 totalParagraphs 表示段落总和
    • iPa 而不是 indexParagraphs 表示一个段落的下标
  • 在编译器不能检查你所用的抽象数据类型的时候,标准前缀能帮助你准确地对类型做出判断

    • paReformat = docReformat 很可能是不对的,因为pa 和 doc 是不同的UDT
  • 主要缺陷是程序员在使用前缀时忽略给变量起有意义的名字。如果ipa已经能非常明确地表示一个段落的下标,那么程序员就不会主动去想类似于ipaActiveDocument这样有意义的名字,为了提高可读性,应停下来为数组下标起一个具有描述性的名字

Creating Short Names That Are Readable

创建具备可读性的短名字

General Abbreviation Guidelines

缩写的一般指导原则

  • 使用标准缩写 (列在字典中的那些)

  • 去掉所有非前置元音

    • computer - cmptr / screen - scrn / apply - appl / integer - intgr
  • 去掉虚词

    • and or the 等
  • 使用每个单词的第一个或前几个字母,之后截断

  • 保留每个单词第一个和最后一个字母

  • 使用名字中的每一个重要单词,最多不超过3个

  • 去除无用的后缀 -ing, ed 第

  • 保留每个音节中最引人注意的发音

  • 确保不要改变变量的含义

  • 反复使用上述技术,直到你把每个变量名的长度缩减到8~20个字符

记住,名字对于代码读者的意义要比对作者更重要

Kinds of Names to Avoid

应避免的名字

避免使用令人误解的名字或缩写

避免使用具有相似含义的名字

  • input 和 inputValue
  • recordNum 和 numRecords
  • fileNumber 和 fileIndex

避免使用具有不同含义但却有相似名字的变量

  • clientRecs 和 clientReps

    • clientRecords 和 clientReports 要比缩写的好

避免使用发音相近的名字

  • wrap 和 rap

避免在名字中使用数字

不要仅靠大小写区分变量名

避免使用标准类型、变量和子程序员的名字

12 Fundamental Data Types 基本数据类型

12 Fundamental Data Types 基本数据类型.png

Numbers in General

数值概论

避免使用"神秘数值"

  • 在程序中出现的、没有经过解释的数值文字量,要使用具名常量来代替它

如需要,可使用硬编码0 和 1

  • for i = 0 to CONSTANT do ... 可接受
  • total = total +1 也可以

预防除零错误

  • 使用除法符号时,要考虑表达式的分母是否有可能是0

使类型转换变舠明显

  • y = x + (float) i

避免混合类型的比较

  • 如果x是浮点数,i是整数,那么下面应避免 if (i = x) then ...

注意编译器的警告

Integers

整数

检查整数除法

检查整数溢出

检查中间结果溢出

Floating-Point Numbers

泛点数

避免数量相差巨大的数之间的加减运算

避免等量判断

处理舍入误差问题

Characters and Strings

字符和字符串

避免使用神秘字符和神秘字符串

  • 使用具名常量

在程序生命周期中尽早决定国际化/本地化策略

如果只支持一种文字语言,考虑使用ISO 8859字符集

如果支持多种语言,使用 Unicode

采用一致的字符串类型转换策略

Boolean Variables

布尔变量

用布尔变量来简化复杂的判断

  • JAVA: 目的不明确的布尔判断
if ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) || ( elementIndex == lastElementIndex ) ) {
    ...
}
  • JAVA: 目的明确的布尔判断
finished = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) );
repeatedEntry = ( elementIndex == lastElementIndex );
if (  finished || repeatedEntry ) {
    ...
}

Enumerated Types

枚举类型

枚举类型是一种允许用英语来描述某一类对象中每一个成员的数据类型

用枚举类型来提高可读性

  • if chosenColor = 1 用下面这样的语句来提高可读性 if chosenColor = Color_Red

    • 每当你看到字面形式的数字时,就应该问问自己,把它换成枚举类型是不是更合理
  • 枚举类型特别适用用于定义子程序参数

    • C++
int result = RetrievePayrollData(
    data,
    EmploymentStatus_CurrentEmployee,
    PayrollType_Salaried,
    SavingsPlan_NoDeduction,
    MedicalCoverage_IncludeDependents
)

将枚举类型作为布尔变量的替换方案

  • 布尔变量往往无法参充分表达它所需要表达的含义。如果成功和失败的具体类型有所增加,对其进行扩展以区分这些情况也是非常容易的

检查非法数值

Select Case screenColor Case Color_Red ... Case Color_Blue ... Case Else Error


### 定义出枚举的第一项和最后一项,以便用于循环边界

- 把第一个和最后一个定义为 Color_First Color_Last,以便循环

### 把枚举类型第一个元素留做非法值

### If Your Language Doesn't Have Enumerated Types
如果你的语言里没有枚举类型

- 可用全局变量或类来模拟它

	- JAVA

class Country { private Country() {}; public static final Country China = new Country(); public static final Country England = new Country(); ... }

class Output { private Output() {}; public static final Output Screen = new Output(); public static final Output Printer = new Output(); ... }


## Arrays
数组

### 考虑用容器来取代数组,或将数组作为化结构来处理

### 永远不要随机访问数组,这很容易变得难于管理易出错,要证明其是否正确也很困难。

### 建议使用集合、栈和队列等按顺序存取元素的数据结构来取代数组




# 13 Unusual Data Types 不常见的数据类型

![13 Unusual Data Types 不常见的数据类型.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c45a6a28c4ec44e89f38cd8c70de9a11~tplv-k3u1fbpfcp-watermark.image)
## Structures
结构体

### 使用其他类型组建的数据

### 在Java和C++里,类有时表现得也像结构体一样(当类完全由公用的数据成员组成,而不包含公用子程序的时候)

### 通常情况下,你会希望创建类而非结构体,这样除了能使用结构体可提供的公用数据成员外,还能利用类所提供的私密性和功能性。但是有时直接操纵成块的数据会十分方便

### 使用结构体的理由

- 用结构体来明确数据关系

	- ```
employee.name = inutName
employee.address = inputAddres
employee.phone = inputPhone

supervisor.title = inputTitle
supervisor.department = inputDepartment
supervisor.bonus = inputBonus
  • 用结构体简化对数据块的操作

    • 想像一下交换两个雇员的数据
    • VB
Dim newEmployee As Employee
Dim oldEmployee As Employee
Dim previousOldEmployee As Employee

三条语句即可交换

previousOldEmployee = oldEmployee
oldEmployee = newEmployee
newEmployee = previousOldEmployee
  • 用结构体简化参数列表

    • EasyWayRoutine( employee )
  • 用结构体来减少维护

    • 由于你在使用结构体的时候是把相关的数据组织在一起的,因此对结构体的修改只会导致到程序做很小的改动

Global Data

全局数据

Common Problems with Global Data

与全局数据有关的常见问题

  • 全局数据的问题

    • 无意间修改了全局数据
    • 奇异和令人激动的别名问题
    • 障碍代码重用
    • 非确定的初始化顺序
    • 破坏了模块化和智力上的可管理性

Reasons to Use Global Data

使用全局数据的理由

  • 保存全局数值

    • 概念上用于整个程序的数据

      • 交互模式或命令行模式、正常模式或错误恢复模式
    • 在整个程序要用的信息

      • 每个子程序都会用到的数据
  • 模拟具名常量 与 枚举类型

  • 简化对极其常用的数据的使用

  • 消除流浪数据

Use Global Data Only as a Last Resort

只在万不得已时才使用全局数据

  • 首先把每个变量设置为局部的,仅伺需要时才把变量设置为全局的

  • 区分全局变量和类变量

    • 只使用访问器子程序来访问类变量

Using Access Routines Instead of Global Data

用访问器子程序来取代全局数据

  • Advantages of Access Routines 访问器子程序的优势

    • 你获得了对数据的集中控制
    • 你可确保对变量的所有引用都得到了保护
    • 你可自动获得信息隐藏的普遍益处
    • 诘问器子程序可很容易地轴变为抽象数据类型
  • How to Use Access Routines 如何使用访问器子程序

    • 把数据隐藏到类里面。用static关键字或它的等价物类声明该数据,以确保只存在该数据的单一实例。写出你可查看并修改该数据的子 程序来。要求类外部代码使用该访问器子程序来访问该数据,而不是直接操作它

    • 要求所有代码通过访问器来存取数据

      • 一个好习惯是要求所有的全局数据都以g_前缀,并除了该变量的访问器外,所有的代码都不可访问具有g_前缀的变量
    • 不要把你所有的全局数据都扔在一处

    • 用锁定来控制对全局变量的访问

    • 在你的访问器子程序里构建一个抽象层

      • 要在问题域这一层次上构建访问器,而不是在细节实现上

      • 直接访问全局数据 与 通过访问器使用全局数据

        • node = node.next

          • account = NextAccount(account)
        • node = node.next

          • employee = NextEmployee(employee)
        • node = node.next

          • rateLevel = NextRateLevel(rateLevel)
        • event = eventQueue[queueFront]

          • event = HighestPriorityEvent()
        • event = eventQueue[queueBack]

          • event = LowestPriorityEvent()
    • 使得对一项数据的所有访问都发生在同一个抽象层上

      • 对复杂数据的不一致操作 与 一致操作

        • event = EventQueue[queueFront]

          • event = HighestPriorityEvent()
        • event = EventQueue[queueBack]

          • event = LowestPriorityEvent()
        • AddEvent(event)

          • AddEvent(event)
        • eventCount = eventCount - 1

          • RemveEvent(event)
  • How to Reduce the Risks of Using Global Data 如何降低使用全局数据的风险

    • 创建一种命名规则来突出全局变量
    • 为全部的全局变量创建一份注释良好的清单
    • 不要用全局变量来存放中间结果
    • 不要把所有的数据都放在一个大对象中并到处传递,以说明你没有使用全局变量